1
2
3 """Auxiliary functions
4
5 Supplemental frequently used function -- either in pype code directly
6 or in user-tasks.
7
8 Author -- James A. Mazer (mazerj@gmail.com)
9
10 """
11
12 import random
13 import sys
14 import time
15 import posixpath
16 import os
17 import re
18 import string
19 import numpy as np
20
21 _tic = None
22
24 """Start timer.
25
26 Benchmark function: start a simple timer (like matlab tic/toc)
27
28 """
29 global _tic
30 _tic = time.time()
31
33 """Stop/lap timer (and perhaps print value).
34
35 Benchmark function: stop & print simple timer (like matlab tic/toc)
36
37 """
38 global _tic
39 if label:
40 sys.stderr.write(label)
41 if _tic:
42 t = time.time()-_tic
43 sys.stderr.write("%f secs" % t)
44 return t
45 else:
46 return None
47
49 """Next available file in sequence.
50
51 :return: next available file from pattern. For example,
52 nextfile('foo.%04d.dat') will return 'foo.0000.dat', then
53 'foo.0001.dat' etc..
54
55 """
56 n = 0
57 while 1:
58 fname = s % n
59 if not posixpath.exists(fname):
60 return fname
61 n = n + 1
62
64 """Last existing file in sequence.
65
66 :return: last opened file from pattern (like nextfile, but returns
67 the last existing file in the sequence).
68
69 """
70 n = 0
71 f = None
72 while 1:
73 fname = s % n
74 if not posixpath.exists(fname):
75 return f
76 f = fname
77 n = n + 1
78
80 - def __init__(self, dateonly=None, sortable=None):
81 months = ['Jan', 'Feb', 'Mar', 'Apr', 'May',
82 'Jun', 'Jul', 'Aug', 'Sep', 'Oct',
83 'Nov', 'Dec' ]
84 (self.y, self.mon, self.d, self.h, self.min, self.s,
85 x1, x2, x3) = time.localtime(time.time())
86 self.monstr = months[self.mon - 1]
87 self.sortable = sortable
88 self.dateonly = dateonly
89
91 if self.sortable:
92 s = "%04d-%02d-%02d" % (self.y, self.mon, self.d)
93 else:
94 s = "%02d-%s-%04d" % (self.d, self.monstr, self.y)
95 if not self.dateonly:
96 s = "%s %02d:%02d:%02d" % (s, self.h, self.min, self.s)
97 return s
98
100 - def __init__(self, filename, autodate=None, autonum=None):
101 if filename:
102 if autodate:
103 filename = "%s.%s" % (filename,
104 Timestamp(dateonly=1, sortable=1))
105 if autonum:
106 n = 0
107 while 1:
108 f = "%s.%03d" % (filename, n)
109 if not posixpath.exists(f):
110 filename = f
111 break
112 n = n + 1
113 self.filename = filename
114 self.write("")
115
117 if self.filename:
118 f = open(self.filename, "a")
119 f.write(line)
120 f.close()
121
122 -def frange(start, stop, step, inclusive=None):
123 """Floating point version of range().
124
125 """
126 r = []
127 if inclusive:
128 stop = stop + step
129 while start < stop:
130 r.append(start)
131 start = start + step
132 return r
133
135 """Random variable generator.
136
137 Generate a uniformly distributed random
138 number in range [center-var, center+var].
139
140 """
141 return random.randint(center - (pvar * center), center + (pvar * center))
142
144 """Pick n integers.
145
146 Select n random integers in the range [lo,hi].
147
148 """
149 if (n > (hi - lo + 1)):
150 n = hi - lo + 1
151 l = []
152 for i in range(0, n):
153 while 1:
154 i = random.randint(lo, hi)
155 if l.count(i) == 0:
156 l.append(i)
157 break
158 return l
159
160 -def urand(min=0, max=1.0, integer=None):
161 """Generate uniformly distributed random numbers (was uniform())
162
163 :param min,max: (float) specifies bounds on range
164
165 :param integer: (boolean) if true, only return integers
166
167 :return: (int | float) random number in specified range
168
169 """
170 v = min + (max - min) * random.random()
171 if integer:
172 return int(round(v))
173 return v
174
175 -def nrand(mean=0, sigma=1.0, integer=None):
176 """Generate normally (Gaussian) distributed random numbers.
177
178 :param mean, sigma: (float) specifies normal/Gaussian dist params
179
180 :param integer: (boolean) if true, only return integers
181
182 :return: (int | float) random number in specified range
183
184 """
185 v = random.normalvariate(mean, sigma)
186 if integer:
187 return int(round(v))
188 return v
189
191 """Generate random numbers from a discrete distribution.
192
193 Assume v represents somethine like a probability density
194 function, with each scalar in v representing the prob. of
195 that index, choose an index from that distribution. Note,
196 the sum(v) MUST equal 1!!!
197
198 *NB* Returned values start from 0, i.e. for v=[0.5, 0.5] possible
199 return values are 0 or 1, with 50/50 chance..
200
201 :param v: (array) frequency distribution
202
203 :return: (int) index into distribution
204
205 """
206
207
208 u = urand(0.0, 1.0)
209 pcumm = 0.0
210 ix = 0
211 for i in range(0, len(v)):
212 pcumm = pcumm + v[i]
213 if u < pcumm:
214 return i
215 return None
216
217 -def _fuzzynums(mean, plus, minus=None, integer=None):
218 """Generate random number between around mean +/- plus/minus.
219
220 This means [mean-minus, mean+plus], distribution is
221 flat/uniform.
222
223 """
224 if integer:
225 return int(round(_fuzzynums(mean, plus, minus, integer=None)))
226 else:
227 if minus is None:
228 minus = plus
229 return urand(mean - minus, mean + plus)
230
232 """Return a random permutation of the input vector.
233
234 :param v: (array) input vector
235
236 :return: randomly permuted version of v
237
238 """
239 l = range(0,len(v))
240 out = []
241 while len(l) > 0:
242 ix = random.randint(0, len(l)-1)
243 out.append(v[l[ix]])
244 del l[ix]
245 return out
246
248 """Pick on element from a vector.
249
250 Tue Jan 7 19:15:43 2003 mazer: This used to return a random
251 element of vector, when available==None, but that was inconsistent
252 with the other junk in this file. Now it always returns an INDEX
253 (small int).
254
255 :param v: (array) vector of elements to select from
256
257 :param available: (boolean vector) -- mask specifying which
258 elements are actually available for selection. This can be
259 used to mask out stimuli that have already been presented etc.
260
261 :return: (int) index of selected element in v
262
263 """
264
265
266 if available is None:
267 return random.randint(0, len(v)-1)
268 else:
269 i = random.randint(0, len(v)-1)
270 j = i;
271 while not available[i]:
272 i = (i + 1) % len(v)
273 if j == i:
274 return None
275 return i
276
278 """Count number of available slots; see pick_one()
279
280 :param available: (boolean vector) see pick_one()
281
282 :return: (int) number of available (non-zero) elements in list
283
284 """
285 n = 0;
286 for i in range(0, len(available)):
287 if available[i]:
288 n = n + 1
289 return n
290
292 """Randomly pick n-elements from vector.
293
294 Pick elements **without replacement** from vector.
295
296 :param v: (array) input vector
297
298 :param n: (int) number of elements to pick
299
300 :return: (array) vector length N containing indices of all
301 selected elements.
302
303 """
304 if n > len(v):
305 raise ValueError, 'pick_n from short v'
306 v = permute(v)
307 return v[0:n]
308
310 """Randomly pick n-elements from vector
311
312 Pick elements **with replacement** from vector.
313
314 *NB* 11-Jul-2005: changed this back to replace returning the
315 selected elements, not the indicies; returning indicies is
316 incompatible with pick_n() and broke zvs10.py ...
317
318 :param v: (array) input vector
319
320 :param n: (int) number elements to pick
321
322 :return: (array) vector length N containing selected elements.
323
324 """
325
326 v = []
327 for i in range(0, n):
328 v.append(vector[pick_one(vector)])
329 return v
330
332 """Expand pype *parameter* string.
333
334 Allowed formats for parameter string (see ptable.py):
335
336 * X+-Y -- uniform dist'd number: [X-Y, X+Y]
337
338 * X+-Y% -- uniform dist'd number: [X-(X*Y/100), X+(X*Y/100)]
339
340 * U[min,max] -- uniform dist'd number between min and max
341
342 * N[mu,sigma] -- normally dist'd number from N(mu, sigma)
343
344 * G[mu,sigma] -- same as N (Gaussian)
345
346 * E[mu] -- exponential of mean mu
347
348 * TE[mu,{max | min,max}] -- exponential of mean mu; values are constrained
349 to be within min:max (by resampling). If only max is specified, min
350 is assumed to be zero
351
352 * ITE[mu,{max | min,max}] -- integer exponential -- like TE[], but
353 discretized to integer values
354
355 * EDC[mu,min,max,nbins] -- discrete exponential of mean mu, contrained
356 to min:max (by resampling). 'nbins' specifies number of discrete points
357 in distribution (edc = exponential dirac comb)
358
359 * start:step[:stride] -- generate non-inclusive range (python
360 style) and pick one at random (default stride is 1)
361
362 * =start:step[:stride] -- generate inclusive range (matlab
363 style) and pick one at random (default stride is 1)
364
365 * [#,#,#,#] -- pick one of these numbers
366
367 * X -- just X
368
369 :param s: (string) parameter specification string
370
371 :param integer: (boolean) if true, return nearest integer
372
373 :return: (float or int) number from specified distirbution
374
375 """
376
377 if integer:
378 return int(round(param_expand(s, integer=None)))
379
380 s = string.strip(s)
381 if len(s) < 1:
382 return None
383
384 if s.lower().startswith('n'):
385 try:
386 (mu, sigma) = eval(s[1:])
387 return np.random.normal(mu, sigma)
388 except:
389
390
391 pass
392
393 if s.lower().startswith('u'):
394 try:
395 (lo, hi) = eval(s[1:])
396 return np.random.uniform(lo, hi)
397 except:
398 pass
399
400 if s.lower().startswith('e'):
401 try:
402 (mu,) = eval(s[1:])
403 return np.random.exponential(mu)
404 except:
405 pass
406
407 if s.lower().startswith('te') or s.lower().startswith('ite'):
408 try:
409
410 if s[0] in 'iI':
411 arg = s[3:]
412 else:
413 arg = s[2:]
414 v = eval(arg)
415 if len(v) == 2:
416 meanval, minval, maxval = v[0], 0.0, v[1]
417 else:
418 meanval, minval, maxval = v[0], v[1], v[2]
419
420 ntries = 0
421 while ntries < 100:
422
423
424
425
426
427
428
429
430 x = np.random.exponential(meanval)
431 if s.lower()[0] == 'i':
432
433
434 x = int(round(x+0.5)-1)
435 if x >= minval and x <= maxval:
436 return x
437 ntries += 1
438 return meanval
439 except:
440 pass
441
442 if s.lower().startswith('edc'):
443 try:
444
445 v = eval(s[3:])
446 meanval, minval, maxval, nbins = v[0], v[1], v[2], v[3]
447
448 if meanval < minval or meanval > maxval:
449 return None
450
451
452
453
454
455
456 ntries = 0
457 while ntries < 100:
458 val = np.random.exponential(meanval)
459 if val >= minval and val <= maxval:
460 binedges = np.linspace(minval,maxval,nbins+1)
461 bincenters = binedges[0:-1] + (binedges[1]-binedges[0])/2
462 val = val-bincenters[0]-1
463 divisor = (bincenters[-1]-(bincenters[0]-1))/nbins
464 val = int((round(val/divisor))*divisor)+(bincenters[0]-1)
465 return val
466 return meanval
467 except:
468 pass
469
470 if s[0] == '[' and s[-1] == ']':
471
472 l = map(float, s[1:-1].split(','))
473 return l[pick_one(l)]
474
475
476
477
478 if s[0] == '=':
479 inc = 1
480 l = s[1:].split(':')
481 else:
482 inc = 0
483 l = s.split(':')
484 if len(l) > 1:
485 if len(l) == 3:
486 start, stop, step = map(float, l)
487 elif len(l) == 2:
488 start, stop = map(float, l)
489 step = 1.0
490 if inc:
491 l = np.arange(start, stop+step, step)
492 else:
493 l = np.arange(start, stop, step)
494 return l[pick_one(l)]
495
496 l = re.split('\+\-', s)
497 if len(l) == 2:
498 a = float(l[0])
499 if len(l[1]) > 0 and l[1][-1] == '%':
500 b = float(l[1][0:-1]) / 100.0
501 b = a * b
502 else:
503 b = float(l[1])
504 return _fuzzynums(a, b)
505
506 return float(s)
507
509 """Pretty-print a parameter table into the info window.
510
511 See info() function.
512
513 :param app: (PypeApp) appliation handle
514
515 :param P: parameter dictionary
516
517 :return: nothing
518
519 """
520 if clearfirst:
521 info(app)
522 keys = P.keys()
523 keys.sort()
524 n = 0
525 while n < len(keys):
526 s = ''
527 while n < len(keys) and len(s) < 25:
528 s = s + "%12s: %-10s" % (keys[n], P[keys[n]])
529 n = n + 1
530 info(app, s)
531
533 """Convert stimulus DIRECTION to an ORIENTATION.
534
535 """
536 ori = (-dir + 90.0)
537 if ori < 0:
538 ori = ori + 360.
539 return ori
540
542 - def __init__(self, conditions, randomize=True, freeze=False):
543 """Bucket to hold a set of conditions or stimulus parameters,.
544
545 Initialize with a list of conditions -- can be any type of
546 sequence object (list, tuple etc) to be provided to the
547 task on-demand.
548
549 :param conditions: (list) list of condition descriptions/parameters
550
551 :param randomize: (bool) automatically randomize order?
552
553 :param freeze: (bool) randomize once and freeze sequence?
554
555 :return: nothing
556
557 """
558 self.conditions = conditions
559 self.randomize = randomize
560 self.block = 0
561 self.frozen_seq = None
562 self.reset()
563 if freeze:
564 self.frozen_seq = self.sequence[:]
565
567 """Reset the bucket -- usually task shouldn't need to call this.
568
569 This resets the bucket back to the starting point, potentially
570 re-randomizing the order. This is automatically called when the
571 bucket on initialization and when the bucket goes empty, so the
572 user should need need to call this directly.
573
574 :return: nothing
575
576 """
577
578 self.sequence = range(len(self.conditions))
579 if self.randomize:
580 if self.frozen_seq is None:
581 self.sequence = permute(self.sequence)
582 else:
583 self.sequence = self.frozen_seq[:]
584
586 """Get next condition from the bucket.
587
588 Automatically refills bucket when it's empty. The index number
589 returned here is what needs to be passed to pop when putting
590 things back in the bucket.
591
592 :return: tuple (index number of condition, condition,
593 current block/repeat)
594
595 """
596
597 if len(self.sequence) < 1:
598 self.reset()
599 self.block += 1
600
601 n = self.sequence[0]
602 self.sequence.pop(0)
603 return n, self.conditions[n], self.block
604
606 """Put condition back in the bucket (if there's an error)
607
608 Automatically refills bucket when it's empty
609
610 :return: nothing
611
612 """
613 if self.randomize and self.frozen_seq is None:
614 self.sequence.append(n)
615 else:
616 self.sequence.insert(0,n)
617
618 if __name__ == '__main__':
619 """
620 bucket = ConditionBucket('a,b,c,d'.split(','),
621 randomize=True, freeze=True)
622 for k in range(10):
623 n, cond, block = bucket.pop()
624 print k, block, cond
625 if k == 5:
626 bucket.push(n)
627 """
628
629 sys.stderr.write('%s should never be loaded as main.\n' % __file__)
630 sys.exit(1)
631