Package pype :: Module pype_aux
[frames] | no frames]

Source Code for Module pype.pype_aux

  1  # -*- Mode: Python; tab-width: 4; py-indent-offset: 4; -*- 
  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   
23 -def tic():
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
32 -def toc(label=None):
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
48 -def nextfile(s):
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
63 -def lastfile(s):
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
79 -class Timestamp(object):
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
90 - def __repr__(self):
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
99 -class Logfile(object):
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
116 - def write(self, line):
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
134 -def vrandint(center, pvar):
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
143 -def pickints(lo, hi, n):
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
190 -def from_discrete_dist(v):
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 # compute cummulative density function 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
231 -def permute(v):
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
247 -def pick_one(v, available=None):
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 290
291 -def pick_n(v, n):
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
309 -def pick_n_replace(v, n):
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
331 -def param_expand(s, integer=None):
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 # should be 'except NameError:' but that doesn't properly 390 # handle unmatched brackets... same below 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 # ite/te[mu,max] or ite/te[mu,min,max] 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 # keep drawing until we get a range-valid value 423 # before 12/18/2012 draw was clipped with min/max 424 # 425 # NOTE: don't draw more than 100 times to avoid 426 # infinite loops -- this usually means bad params 427 # entered by user, or bad values during changes. In 428 # that case, just return mean.. 429 # 430 x = np.random.exponential(meanval) 431 if s.lower()[0] == 'i': 432 # shift by +0.5 then -1 to to avoid problems with the 433 # bin from -0.5-0.5 being only half full due to exp>0 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 # edc[mu,min,max,nbins] (ACM's exponential dirac comb) 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 # search for a value until it falls within the specified range 452 # NOTE: don't draw more than 100 times to avoid 453 # infinite loops -- this usually means bad params 454 # entered by user, or bad values during changes. In 455 # that case, just return mean.. 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 # list syntax: pick one from [#,#,#,#] 472 l = map(float, s[1:-1].split(',')) 473 return l[pick_one(l)] 474 475 # if 'slice' syntax is used, generate the slice using range 476 # and pick one element randomly. e.g., '1:10:2' would pick 477 # 1, 3, 5, 7 or 9 with equal prob. 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
508 -def showparams(app, P, clearfirst=1):
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
532 -def dir2ori(dir):
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
541 -class ConditionBucket(object):
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
566 - def reset(self):
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
585 - def pop(self):
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
605 - def push(self, n):
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