root/trunk/src/stgen.py

Revision 356, 29.2 kB (checked in by apdavison, 2 months ago)

Changed the behaviour of the AnalogSignal constructor. Before, t_start and t_stop were used to throw away data, on the assumption that the signal always started at t=0 - see [45]. Now, these arguments are used as metadata, and we check that they are consistent with the number of elements in the signal array.

Also added some checks that all the AnalogSignals in an AnalogSignalList are consistent in terms of length, dt, etc.

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 not check_dependency('rpy'):
89         raise ImportError("gamma_hazard requires RPy (http://rpy.sourceforge.net/)")
90
91     from rpy import r
92
93     # scipy.special.gammaincc has numerical problems
94     #Hpre = -log(scipy.special.gammaincc(a,(x-dt)/b))
95     #Hpost = -log(scipy.special.gammaincc(a,(x+dt)/b))
96
97     # reverting to the good old r.pgamma
98     Hpre = -r.pgamma(x-dt,shape=a,scale=b,lower=False,log=True)
99     Hpost = -r.pgamma(x+dt,shape=a,scale=b,lower=False,log=True)
100     val =  0.5*(Hpost-Hpre)/dt
101
102     return val
103
104
105    
106
107 class StGen:
108
109     def __init__(self, rng=None, seed=None):
110         """
111         Stochastic Process Generator
112         ============================
113
114         Object to generate stochastic processes of various kinds
115         and return them as SpikeTrain or AnalogSignal objects.
116       
117
118         Inputs:
119             rng - The random number generator state object (optional). Can be None, or
120                   a numpy.random.RandomState object, or an object with the same
121                   interface.
122
123             seed - A seed for the rng (optional).
124
125         If rng is not None, the provided rng will be used to generate random numbers,
126         otherwise StGen will create its own random number generator.
127         If a seed is provided, it is passed to rng.seed(seed)
128
129         Examples:
130             >> x = StGen()
131
132
133
134         StGen Methods:
135
136         Spiking point processes:
137         ------------------------
138  
139         poisson_generator - homogeneous Poisson process
140         inh_poisson_generator - inhomogeneous Poisson process (time varying rate)
141         inh_gamma_generator - inhomogeneous Gamma process (time varying a,b)
142         inh_adaptingmarkov_generator - inhomogeneous adapting markov process (time varying)
143         inh_2Dadaptingmarkov_generator - inhomogeneous adapting and
144                                          refractory markov process (time varying)
145
146         Continuous time processes:
147         --------------------------
148
149         OU_generator - Ohrnstein-Uhlenbeck process
150         
151
152         See also:
153           shotnoise_fromspikes
154
155         """
156
157         if rng==None:
158             self.rng = numpy.random.RandomState()
159         else:
160             self.rng = rng
161
162         if seed != None:
163             self.rng.seed(seed)
164
165     def seed(self,seed):
166         """ seed the gsl rng with a given seed """
167         self.rng.seed(seed)
168
169
170     def poisson_generator(self, rate, t_start=0.0, t_stop=1000.0, array=False,debug=False):
171         """
172         Returns a SpikeTrain whose spikes are a realization of a Poisson process
173         with the given rate (Hz) and stopping time t_stop (milliseconds).
174
175         Note: t_start is always 0.0, thus all realizations are as if
176         they spiked at t=0.0, though this spike is not included in the SpikeList.
177
178         Inputs:
179             rate    - the rate of the discharge (in Hz)
180             t_start - the beginning of the SpikeTrain (in ms)
181             t_stop  - the end of the SpikeTrain (in ms)
182             array   - if True, a numpy array of sorted spikes is returned,
183                       rather than a SpikeTrain object.
184
185         Examples:
186             >> gen.poisson_generator(50, 0, 1000)
187             >> gen.poisson_generator(20, 5000, 10000, array=True)
188         
189         See also:
190             inh_poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator
191         """
192
193         #number = int((t_stop-t_start)/1000.0*2.0*rate)
194         
195         # less wasteful than double length method above
196         n = (t_stop-t_start)/1000.0*rate
197         number = numpy.ceil(n+3*numpy.sqrt(n))
198         if number<100:
199             number = min(5+numpy.ceil(2*n),100)
200        
201         if number > 0:
202             isi = self.rng.exponential(1.0/rate, number)*1000.0
203             if number > 1:
204                 spikes = numpy.add.accumulate(isi)
205             else:
206                 spikes = isi
207         else:
208             spikes = numpy.array([])
209
210         spikes+=t_start
211         i = numpy.searchsorted(spikes, t_stop)
212
213         extra_spikes = []
214         if i==len(spikes):
215             # ISI buf overrun
216             
217             t_last = spikes[-1] + self.rng.exponential(1.0/rate, 1)[0]*1000.0
218
219             while (t_last<t_stop):
220                 extra_spikes.append(t_last)
221                 t_last += self.rng.exponential(1.0/rate, 1)[0]*1000.0
222            
223             spikes = numpy.concatenate((spikes,extra_spikes))
224
225             if debug:
226                 print "ISI buf overrun handled. len(spikes)=%d, len(extra_spikes)=%d" % (len(spikes),len(extra_spikes))
227
228
229         else:
230             spikes = numpy.resize(spikes,(i,))
231
232         if not array:
233             spikes = SpikeTrain(spikes, t_start=t_start,t_stop=t_stop)
234
235
236         if debug:
237             return spikes, extra_spikes
238         else:
239             return spikes
240
241
242     def inh_poisson_generator(self, rate, t, t_stop, array=False):
243         """
244         Returns a SpikeList whose spikes are a realization of an inhomogeneous
245         poisson process (dynamic rate). The implementation uses the thinning
246         method, as presented in the references.
247
248         Inputs:
249             rate   - an array of the rates (Hz) where rate[i] is active on interval
250                      [t[i],t[i+1]]
251             t      - an array specifying the time bins (in milliseconds) at which to
252                      specify the rate
253             t_stop - length of time to simulate process (in ms)
254             array  - if True, a numpy array of sorted spikes is returned,
255                      rather than a SpikeList object.
256
257         Note:
258             t_start=t[0]
259
260         References:
261
262         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
263         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
264         Neural Comput. 2007 19: 2958-3010.
265         
266         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
267
268         Examples:
269             >> time = arange(0,1000)
270             >> stgen.inh_poisson_generator(time,sin(time), 1000)
271
272         See also:
273             poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator
274         """
275
276         if numpy.shape(t)!=numpy.shape(rate):
277             raise ValueError('shape mismatch: t,rate must be of the same shape')
278
279         # get max rate and generate poisson process to be thinned
280         rmax = numpy.max(rate)
281         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
282
283         # return empty if no spikes
284         if len(ps) == 0:
285             return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
286        
287         # gen uniform rand on 0,1 for each spike
288         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
289
290         # instantaneous rate for each spike
291         
292         idx=numpy.searchsorted(t,ps)-1
293         spike_rate = rate[idx]
294
295         # thin and return spikes
296         spike_train = ps[rn<spike_rate/rmax]
297
298         if array:
299             return spike_train
300
301         return SpikeTrain(spike_train, t_start=t[0],t_stop=t_stop)
302
303
304
305     def _inh_gamma_generator_python(self, a, b, t, t_stop, array=False):
306         """
307         Returns a SpikeList whose spikes are a realization of an inhomogeneous gamma process
308         (dynamic rate). The implementation uses the thinning method, as presented in the
309         references.
310
311         Inputs:
312             a,b    - arrays of the parameters of the gamma PDF where a[i] and b[i]
313                      will be active on interval [t[i],t[i+1]]
314             t      - an array specifying the time bins (in milliseconds) at which to
315                      specify the rate
316             t_stop - length of time to simulate process (in ms)
317             array  - if True, a numpy array of sorted spikes is returned,
318                      rather than a SpikeList object.
319
320         Note:
321             t_start=t[0]
322             a is a dimensionless quantity > 0, but typically on the order of 2-10.
323             a = 1 results in a poisson process.
324             b is assumed to be in units of 1/Hz (seconds).
325
326         References:
327
328         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
329         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
330         Neural Comput. 2007 19: 2958-3010.
331         
332         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
333
334         Examples:
335             See source:trunk/examples/stgen/inh_gamma_psth.py
336
337         See also:
338             inh_poisson_generator, gamma_hazard
339         """
340
341         from numpy import shape
342
343         if shape(t)!=shape(a) or shape(a)!=shape(b):
344             raise ValueError('shape mismatch: t,a,b must be of the same shape')
345
346         # get max rate and generate poisson process to be thinned
347         rmax = numpy.max(1.0/b)
348         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
349
350         # return empty if no spikes
351         if len(ps) == 0:
352             return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
353
354
355         # gen uniform rand on 0,1 for each spike
356         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
357
358         # instantaneous a,b for each spike
359
360         idx=numpy.searchsorted(t,ps)-1
361         spike_a = a[idx]
362         spike_b = b[idx]
363
364         keep = numpy.zeros(shape(ps),bool)
365
366         # thin spikes
367
368         i = 0
369         t_last = 0.0
370         t_i = 0
371
372         while(i<len(ps)):
373             # find index in "t" time
374             t_i = numpy.searchsorted(t[t_i:],ps[i],'right')-1+t_i
375             if rn[i]<gamma_hazard((ps[i]-t_last)/1000.0,a[t_i],b[t_i])/rmax:
376                 # keep spike
377                 t_last = ps[i]
378                 keep[i] = True
379             i+=1
380
381
382         spike_train = ps[keep]
383
384         if array:
385             return spike_train
386
387         return SpikeTrain(spike_train, t_start=t[0],t_stop=t_stop)
388
389
390     # use slow python implementation for the time being
391     # TODO: provide optimized C/weave implementation if possible
392
393
394     def _inh_gamma_generator_unimplemented(self, a, b, t, t_stop, array=False):
395         raise Exception("inh_gamma_generator is disabled as dependency RPy was not found.")
396
397     if check_dependency('rpy'):
398         inh_gamma_generator = _inh_gamma_generator_python
399     else:
400         inh_gamma_generator = _inh_gamma_generator_unimplemented
401
402
403
404     def _inh_adaptingmarkov_generator_python(self, a, bq, tau, t, t_stop, array=False):
405
406         """
407         Returns a SpikeList whose spikes are an inhomogeneous
408         realization (dynamic rate) of the so-called adapting markov
409         process (see references). The implementation uses the thinning
410         method, as presented in the references.
411
412         This is the 1d implementation, with no relative refractoriness.
413         For the 2d implementation with relative refractoriness,
414         see the inh_2dadaptingmarkov_generator.
415
416         Inputs:
417             a,bq    - arrays of the parameters of the hazard function where a[i] and bq[i]
418                      will be active on interval [t[i],t[i+1]]
419             tau    - the time constant of adaptation (in milliseconds).
420             t      - an array specifying the time bins (in milliseconds) at which to
421                      specify the rate
422             t_stop - length of time to simulate process (in ms)
423             array  - if True, a numpy array of sorted spikes is returned,
424                      rather than a SpikeList object.
425
426         Note:
427             - t_start=t[0]
428
429             - a is in units of Hz.  Typical values are available
430               in Fig. 1 of Muller et al 2007, a~5-80Hz (low to high stimulus)
431
432             - bq here is taken to be the quantity b*q_s in Muller et al 2007, is thus
433               dimensionless, and has typical values bq~3.0-1.0 (low to high stimulus)
434
435             - tau_s has typical values on the order of 100 ms
436
437
438         References:
439
440         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
441         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
442         Neural Comput. 2007 19: 2958-3010.
443         
444         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
445
446         Examples:
447             See source:trunk/examples/stgen/inh_2Dmarkov_psth.py
448
449         
450         See also:
451             inh_poisson_generator, inh_gamma_generator, inh_2dadaptingmarkov_generator
452
453         """
454
455         from numpy import shape
456
457         if shape(t)!=shape(a) or shape(a)!=shape(bq):
458             raise ValueError('shape mismatch: t,a,b must be of the same shape')
459
460         # get max rate and generate poisson process to be thinned
461         rmax = numpy.max(a)
462         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
463
464         isi = numpy.zeros_like(ps)
465         isi[1:] = ps[1:]-ps[:-1]
466         isi[0] = ps[0] #-0.0 # assume spike at 0.0
467
468         # return empty if no spikes
469         if len(ps) == 0:
470             return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
471
472
473         # gen uniform rand on 0,1 for each spike
474         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
475
476         # instantaneous a,bq for each spike
477
478         idx=numpy.searchsorted(t,ps)-1
479         spike_a = a[idx]
480         spike_bq = bq[idx]
481
482         keep = numpy.zeros(shape(ps),bool)
483
484         # thin spikes
485
486         i = 0
487         t_last = 0.0
488         t_i = 0
489         # initial adaptation state is unadapted, i.e. large t_s
490         t_s = 1000*tau
491
492         while(i<len(ps)):
493             # find index in "t" time, without searching whole array each time
494             t_i = numpy.searchsorted(t[t_i:],ps[i],'right')-1+t_i
495
496             # evolve adaptation state
497             t_s+=isi[i]
498
499             if rn[i]<a[t_i]*numpy.exp(-bq[t_i]*numpy.exp(-t_s/tau))/rmax:
500                 # keep spike
501                 keep[i] = True
502                 # remap t_s state
503                 t_s = -tau*numpy.log(numpy.exp(-t_s/tau)+1)
504             i+=1
505
506
507         spike_train = ps[keep]
508
509         if array:
510             return spike_train
511
512         return SpikeTrain(spike_train, t_start=t[0],t_stop=t_stop)
513
514
515     # use slow python implementation for the time being
516     # TODO: provide optimized C/weave implementation if possible
517
518        
519     inh_adaptingmarkov_generator = _inh_adaptingmarkov_generator_python
520
521
522     def _inh_2Dadaptingmarkov_generator_python(self, a, bq, tau_s, tau_r, qrqs, t, t_stop, array=False):
523
524         """
525         Returns a SpikeList whose spikes are an inhomogeneous
526         realization (dynamic rate) of the so-called 2D adapting markov
527         process (see references).  2D implies the process has two
528         states, an adaptation state, and a refractory state, both of
529         which affect its probability to spike.  The implementation
530         uses the thinning method, as presented in the references.
531
532         For the 1d implementation, with no relative refractoriness,
533         see the inh_adaptingmarkov_generator.
534
535         Inputs:
536             a,bq    - arrays of the parameters of the hazard function where a[i] and bq[i]
537                      will be active on interval [t[i],t[i+1]]
538             tau_s    - the time constant of adaptation (in milliseconds).
539             tau_r    - the time constant of refractoriness (in milliseconds).
540             qrqs     - the ratio of refractoriness conductance to adaptation conductance.
541                        typically on the order of 200.
542             t      - an array specifying the time bins (in milliseconds) at which to
543                      specify the rate
544             t_stop - length of time to simulate process (in ms)
545             array  - if True, a numpy array of sorted spikes is returned,
546                      rather than a SpikeList object.
547
548         Note:
549             - t_start=t[0]
550
551             - a is in units of Hz.  Typical values are available
552               in Fig. 1 of Muller et al 2007, a~5-80Hz (low to high stimulus)
553
554             - bq here is taken to be the quantity b*q_s in Muller et al 2007, is thus
555               dimensionless, and has typical values bq~3.0-1.0 (low to high stimulus)
556
557             - qrqs is the quantity q_r/q_s in Muller et al 2007,
558               where a value of qrqs = 3124.0nS/14.48nS = 221.96 was used.
559
560             - tau_s has typical values on the order of 100 ms
561             - tau_r has typical values on the order of 2 ms
562
563
564         References:
565
566         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
567         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
568         Neural Comput. 2007 19: 2958-3010.
569         
570         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
571
572         Examples:
573             See source:trunk/examples/stgen/inh_2Dmarkov_psth.py
574         
575         See also:
576             inh_poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator
577
578         """
579
580         from numpy import shape
581
582         if shape(t)!=shape(a) or shape(a)!=shape(bq):
583             raise ValueError('shape mismatch: t,a,b must be of the same shape')
584
585         # get max rate and generate poisson process to be thinned
586         rmax = numpy.max(a)
587         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
588
589         isi = numpy.zeros_like(ps)
590         isi[1:] = ps[1:]-ps[:-1]
591         isi[0] = ps[0] #-0.0 # assume spike at 0.0
592
593         # return empty if no spikes
594         if len(ps) == 0:
595             return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
596
597
598         # gen uniform rand on 0,1 for each spike
599         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
600
601         # instantaneous a,bq for each spike
602
603         idx=numpy.searchsorted(t,ps)-1
604         spike_a = a[idx]
605         spike_bq = bq[idx]
606
607         keep = numpy.zeros(shape(ps),bool)
608
609         # thin spikes
610
611         i = 0
612         t_last = 0.0
613         t_i = 0
614         # initial adaptation state is unadapted, i.e. large t_s
615         t_s = 1000*tau_s
616         t_r = 1000*tau_s
617
618         while(i<len(ps)):
619             # find index in "t" time, without searching whole array each time
620             t_i = numpy.searchsorted(t[t_i:],ps[i],'right')-1+t_i
621
622             # evolve adaptation state
623             t_s+=isi[i]
624             t_r+=isi[i]
625
626             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:
627                 # keep spike
628                 keep[i] = True
629                 # remap t_s state
630                 t_s = -tau_s*numpy.log(numpy.exp(-t_s/tau_s)+1)
631                 t_r = -tau_r*numpy.log(numpy.exp(-t_r/tau_r)+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_2Dadaptingmarkov_generator = _inh_2Dadaptingmarkov_generator_python
648
649
650
651
652
653
654
655     def _OU_generator_python(self, dt, tau, sigma, y0, t_start=0.0, t_stop=1000.0, array=False,time_it=False):
656         """
657         Generates an Orstein Ulbeck process using the forward euler method. The function returns
658         an AnalogSignal object.
659         
660         Inputs:
661             dt      - the time resolution in milliseconds of th signal
662             tau     - the correlation time in milliseconds
663             sigma   - std dev of the process
664             y0      - initial value of the process, at t_start
665             t_start - start time in milliseconds
666             t_stop  - end time in milliseconds
667             array   - if True, the functions returns the tuple (y,t)
668                       where y and t are the OU signal and the time bins, respectively,
669                       and are both numpy arrays.
670         
671         Examples:
672             >> stgen.OU_generator(0.1, 2, 3, 0, 0, 10000)
673
674         See also:
675             OU_generator_weave1
676         """
677
678         import time
679
680         if time_it:
681             t1 = time.time()
682
683         t     = numpy.arange(t_start,t_stop,dt)
684         N     = len(t)
685         y     = numpy.zeros(N,float)
686         gauss = self.rng.standard_normal(N-1)
687         y[0]  = y0
688         fac   = dt/tau
689         noise = numpy.sqrt(2*fac)*sigma
690        
691
692         # python loop... bad+slow!
693         for i in xrange(1,N):
694             y[i] = y[i-1]+fac*(y0-y[i-1])+noise*gauss[i-1]
695
696         if time_it:
697             print time.time()-1
698        
699         if array:
700             return (y,t)
701
702         result = AnalogSignal(y, dt, t_start, t_stop)
703         return result
704        
705     # use slow python implementation for the time being
706     # TODO: provide optimized C/weave implementation if possible
707
708
709     def _OU_generator_python2(self, dt, tau, sigma, y0, t_start=0.0, t_stop=1000.0, array=False,time_it=False):
710         """
711         Generates an Orstein Ulbeck process using the forward euler method. The function returns
712         an AnalogSignal object.
713         
714         Inputs:
715             dt      - the time resolution in milliseconds of th signal
716             tau     - the correlation time in milliseconds
717             sigma   - std dev of the process
718             y0      - initial value of the process, at t_start
719             t_start - start time in milliseconds
720             t_stop  - end time in milliseconds
721             array   - if True, the functions returns the tuple (y,t)
722                       where y and t are the OU signal and the time bins, respectively,
723                       and are both numpy arrays.
724         
725         Examples:
726             >> stgen.OU_generator(0.1, 2, 3, 0, 0, 10000)
727
728         See also:
729             OU_generator_weave1
730         """
731
732         import time
733
734         if time_it:
735             t1 = time.time()
736
737         t     = numpy.arange(t_start,t_stop,dt)
738         N     = len(t)
739         y     = numpy.zeros(N,float)
740         y[0]  = y0
741         fac   = dt/tau
742         gauss = fac*y0+numpy.sqrt(2*fac)*sigma*self.rng.standard_normal(N-1)
743         mfac = 1-fac
744        
745         # python loop... bad+slow!
746         for i in xrange(1,N):
747             idx = i-1
748             y[i] = y[idx]*mfac+gauss[idx]
749
750         if time_it:
751             print time.time()-t1
752
753         if array:
754             return (y,t)
755
756         result = AnalogSignal(y, dt, t_start, t_stop)
757         return result
758        
759     # use slow python implementation for the time being
760     # TODO: provide optimized C/weave implementation if possible
761
762
763     def OU_generator_weave1(self, dt,tau,sigma,y0,t_start=0.0,t_stop=1000.0,time_it=False):
764         """
765         Generates an Orstein Ulbeck process using the forward euler method. The function returns
766         an AnalogSignal object.
767
768         OU_generator_weave1, as opposed to OU_generator, uses scipy.weave
769         and is thus much faster.
770         
771         Inputs:
772             dt      - the time resolution in milliseconds of th signal
773             tau     - the correlation time in milliseconds
774             sigma   - std dev of the process
775             y0      - initial value of the process, at t_start
776             t_start - start time in milliseconds
777             t_stop  - end time in milliseconds
778             array   - if True, the functions returns the tuple (y,t)
779                       where y and t are the OU signal and the time bins, respectively,
780                       and are both numpy arrays.
781         
782         Examples:
783             >> stgen.OU_generator_weave1(0.1, 2, 3, 0, 0, 10000)
784
785         See also:
786             OU_generator
787         """
788         import scipy.weave
789        
790         import time
791
792         if time_it:
793             t1 = time.time()
794
795
796         t = numpy.arange(t_start,t_stop,dt)
797         N = len(t)
798         y = numpy.zeros(N,float)
799         y[0] = y0
800         fac = dt/tau
801         gauss = fac*y0+numpy.sqrt(2*fac)*sigma*self.rng.standard_normal(N-1)
802        
803        
804         # python loop... bad+slow!
805         #for i in xrange(1,len(t)):
806         #    y[i] = y[i-1]+dt/tau*(y0-y[i-1])+numpy.sqrt(2*dt/tau)*sigma*numpy.random.normal()
807
808         # use weave instead
809
810         code = """
811
812         double f = 1.0-fac;
813
814         for(int i=1;i<Ny[0];i++) {
815           y(i) = y(i-1)*f + gauss(i-1);
816         }
817         """
818
819         scipy.weave.inline(code,['y', 'gauss', 'fac'],
820                      type_converters=scipy.weave.converters.blitz)
821
822
823         if time_it:
824             print 'Elapsed ',time.time()-t1,' seconds.'
825
826         if array:
827             return (y,t)
828
829         result = AnalogSignal(y,dt,t_start,t_stop)
830         return result
831
832
833
834     OU_generator = _OU_generator_python2
835
836     # TODO: optimized inhomogeneous OU generator
837
838
839 # TODO: have a array generator with spatio-temporal correlations
840
841 # TODO fix shotnoise stuff below  ... and write tests
842
843 # Operations on spike trains
844
845
846 def shotnoise_fromspikes(spike_train,q,tau,dt=0.1,t_start=None, t_stop=None,array=False, eps = 1.0e-8):
847     """
848     Convolves the provided spike train with shot decaying exponentials
849     yielding so called shot noise if the spike train is Poisson-like. 
850     Returns an AnalogSignal if array=False, otherwise (shotnoise,t) as numpy arrays.
851
852    Inputs:
853       spike_train - a SpikeTrain object
854       q - the shot jump for each spike
855       tau - the shot decay time constant in milliseconds
856       dt - the resolution of the resulting shotnoise in milliseconds
857       t_start - start time of the resulting AnalogSignal
858                 If unspecified, t_start of spike_train is used
859       t_stop  - stop time of the resulting AnalogSignal
860                 If unspecified, t_stop of spike_train is used
861       array - if True, returns (shotnoise,t) as numpy arrays, otherwise an AnalogSignal.
862       eps - a numerical parameter indicating at what value of
863       the shot kernal the tail is cut.  The default is usually fine.
864
865    Note:
866       Spikes in spike_train before t_start are taken into account in the convolution.
867
868    Examples:
869       >> stg = stgen.StGen()
870       >> st = stg.poisson_generator(10.0,0.0,1000.0)
871       >> g_e = shotnoise_fromspikes(st,2.0,10.0,dt=0.1)
872
873
874    See also:
875       poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator, OU_generator ...
876    """
877
878     st = spi