root/trunk/src/stgen.py

Revision 472, 33.9 KB (checked in by emuller, 16 months ago)

stgen._gen_g_add ... got working again ... an alternative to shotnoise_fromspike which can be faster if the number of spikes is small ...

Line 
1"""
2NeuroTools.stgen
3================
4
5A collection of tools for stochastic process generation.
6
7
8Classes
9-------
10
11StGen - Object to generate stochastic processes of various kinds
12        and return them as SpikeTrain or AnalogSignal objects.
13
14
15Functions
16---------
17
18shotnoise_fromspikes - Convolves the provided spike train with shot decaying exponential.
19
20gamma_hazard - Compute the hazard function for a gamma process with parameters a,b.
21"""
22
23
24from NeuroTools import check_dependency
25from signals import SpikeTrain, AnalogSignal
26from numpy import array, log
27import numpy
28
29
30def 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
66def 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
122class 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
974def 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
1060def _gen_g_add(spikes,q,tau,t,eps = 1.0e-8):
1061    """
1062
1063    spikes is a SpikeTrain object
1064
1065    """
1066
1067    #spikes = poisson_generator(rate,t[-1])
1068
1069    gd_s = numpy.zeros(t.shape,float)
1070
1071    dt = t[1]-t[0]
1072
1073    # time of vanishing significance
1074    vs_t = -tau*numpy.log(eps/q)
1075    kern = q*numpy.exp(-numpy.arange(0.0,vs_t,dt)/tau)
1076
1077    vs_idx = len(kern)
1078
1079    idx = numpy.clip(numpy.searchsorted(t,spikes.spike_times),0,len(t)-1)
1080    idx2 = numpy.clip(idx+vs_idx,0,len(gd_s))
1081    idx3 = idx2-idx
1082
1083    for i in xrange(len(idx)):
1084
1085        gd_s[idx[i]:idx2[i]] += kern[0:idx3[i]]
1086
1087    return gd_s
1088
Note: See TracBrowser for help on using the browser.