Package topo :: Package misc :: Module util
[hide private]
[frames] | no frames]

Source Code for Module topo.misc.util

  1  """ 
  2  General utility functions and classes. 
  3   
  4  $Id: util.py 11304 2010-07-27 16:35:22Z ceball $ 
  5  """ 
  6  __version__='$Revision: 11304 $' 
  7   
  8  import re 
  9  import random 
 10  import numpy 
 11  import functools 
 12   
 13   
 14   
15 -def NxN(tuple):
16 """ 17 Converts a tuple (X1,X2,...,Xn) to a string 'X1xX2x...xXn' 18 """ 19 return 'x'.join([`N` for N in tuple])
20 21 22 23 enum = enumerate 24 25
26 -class Struct:
27 """ 28 A simple structure class, taking keyword args and assigning them to attributes. 29 30 For instance: 31 32 s = Struct(foo='a',bar=1) 33 >>> s.foo 34 'a' 35 >>> s.bar 36 1 37 38 39 From http://www.norvig.com/python-iaq.html 40 """
41 - def __init__(self, **entries): self.__dict__.update(entries)
42
43 - def __repr__(self):
44 # 45 args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()] 46 return 'Struct(%s)' % ', '.join(args)
47 48 49
50 -def flat_indices(shape):
51 """ 52 Returns a list of the indices needed to address or loop over all 53 the elements of a 1D or 2D matrix with the given shape. E.g.: 54 55 flat_indices((3,)) == [0,1,2] 56 """ 57 if len(shape) == 1: 58 return [(x,) for x in range(shape[0])] 59 else: 60 rows,cols = shape 61 return [(r,c) for r in range(rows) for c in range(cols)]
62 63 64
65 -def flatten(l):
66 """ 67 Flattens a list. 68 69 Written by Bearophile as published on the www.python.org newsgroups. 70 Pulled into Topographica 3/5/2005. 71 """ 72 if type(l) != list: 73 return l 74 else: 75 result = [] 76 stack = [] 77 stack.append((l,0)) 78 while len(stack) != 0: 79 sequence, j = stack.pop(-1) 80 while j < len(sequence): 81 if type(sequence[j]) != list: 82 k, j, lens = j, j+1, len(sequence) 83 while j < len(sequence) and \ 84 (type(sequence[j]) != list): 85 j += 1 86 result.extend(sequence[k:j]) 87 else: 88 stack.append((sequence, j+1)) 89 sequence, j = sequence[j], 0 90 return result
91 92 93 94 """ 95 Return the cross-product of a variable number of lists (e.g. of a list of lists). 96 97 Use to obtain permutations, e.g. 98 l1=[a,b] 99 l2=[c,d] 100 cross_product([l1,l2]) = 101 [[a,c], [a,d], [b,c], [b,d]] 102 103 104 From: 105 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/159975 106 """ 107 # Need to re-write so someone other than Python knows what might happen when this runs 108 cross_product=lambda ss,row=[],level=0: len(ss)>1 \ 109 and reduce(lambda x,y:x+y,[cross_product(ss[1:],row+[i],level+1) for i in ss[0]]) \ 110 or [row+[i] for i in ss[0]] 111 112 113 114 # JABALERT: Should frange be replaced with numpy.arange or numpy.linspace?
115 -def frange(start, end=None, inc=1.0, inclusive=False):
116 """ 117 A range function that accepts float increments. 118 119 Otherwise, works just as the inbuilt range() function. If 120 inclusive is False, as in the default, the range is exclusive (not 121 including the end value), as in the inbuilt range(). If inclusive 122 is true, the range may include the end value. 123 124 'All theoretic restrictions apply, but in practice this is 125 more useful than in theory.' 126 127 From: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66472 128 """ 129 if end == None: 130 end = start + 0.0 131 start = 0.0 132 133 # Increments of zero would lead to an infinite loop, which can happen if 134 # this is mistakenly called with a integer-based rational expression like 1/2. 135 assert ((inc>0 and start<=end) or (inc<0 and start>=end)) 136 137 L = [] 138 while 1: 139 next = start + len(L) * inc 140 if inclusive: 141 if inc > 0 and next > end: break 142 elif inc < 0 and next < end: break 143 else: 144 if inc > 0 and next >= end: break 145 elif inc < 0 and next <= end: break 146 L.append(next) 147 148 return L
149 150 151
152 -def shortclassname(x):
153 """ 154 Returns the class name of x as a string with the leading package information removed. 155 156 E.g. if x is of type "<class 'topo.base.sheet.Sheet'>", returns 157 "Sheet" 158 """ 159 return re.sub("'>","",re.sub(".*[.]","",repr(type(x))))
160 161 162
163 -def profile(command,n=50,sorting=('cumulative','time'),strip_dirs=False):
164 """ 165 Profile the given command (supplied as a string), printing 166 statistics about the top n functions when ordered according to 167 sorting. 168 169 sorting defaults to ordering by cumulative time and then internal 170 time; see http://docs.python.org/lib/profile-stats.html for other 171 sorting options. 172 173 By default, the complete paths of files are not shown. If there 174 are multiple files with the same name, you might wish to set 175 strip_dirs=False to make it easier to follow the output. 176 177 178 Examples: 179 180 - profile loading a simulation: 181 profile('execfile("examples/hierarchical.ty")') 182 183 - profile running an already loaded simulation: 184 profile('topo.sim.run(10)') 185 186 - profile running a whole simulation: 187 profile('execfile("examples/lissom_oo_or.ty");topo.sim.run(20000)') 188 189 - profile running a simulation, but from the commandline: 190 ./topographica examples/hierarchical.ty -c "from topo.misc.util import profile; profile('topo.sim.run(10)')" 191 """ 192 # This function simply wraps some functions from the cProfile 193 # module, making profiling easier. 194 import cProfile, pstats 195 196 197 # CB: leaves around "filename": should give this a proper name and maybe 198 # put in /tmp/ and maybe allow someone to choose where to save it 199 prof = cProfile.run(command,'filename') 200 prof_stats = pstats.Stats('filename') 201 202 if strip_dirs:prof_stats.strip_dirs() 203 204 prof_stats.sort_stats(*sorting).print_callees(n) 205 ### the above lets us see which times are due to which calls 206 ### unambiguously, while the version below only reports total time 207 ### spent in each object, not the time due to that particular 208 ### call. 209 prof_stats.sort_stats(*sorting).print_stats(n)
210 211 212 213
214 -def weighted_sample(seq,weights=[]):
215 """ 216 Select randomly from the given sequence. 217 218 The weights, if given, should be a sequence the same length as 219 seq, as would be passed to weighted_sample_idx(). 220 """ 221 if not weights: 222 return seq[random.randrange(len(seq))] 223 else: 224 assert len(weights) == len(seq) 225 return seq[weighted_sample_idx(weights)]
226 227
228 -def weighted_sample_idx(weights):
229 """ 230 Return an integer generated by sampling the discrete distribution 231 represented by the sequence of weights. 232 233 The integer will be in the range [0,len(weights)). The weights 234 need not sum to unity, but can contain any non-negative values 235 (e.g., [1 1 1 100] is a valid set of weights). 236 237 To use weights from a 2D numpy array w, specify w.ravel() (not the 238 w.flat iterator). 239 """ 240 total = sum(weights) 241 if total == 0: 242 return random.randrange(len(weights)) 243 index = random.random() * total 244 accum = 0 245 for i,x in enumerate(weights): 246 accum += x 247 if index < accum: 248 return i
249 250
251 -def idx2rowcol(idx,shape):
252 """ 253 Given a flat matrix index and a 2D matrix shape, return the (row,col) 254 coordinates of the index. 255 """ 256 assert len(shape) == 2 257 rows,cols = shape 258 259 return idx/cols,idx%cols
260 261 262
263 -def rowcol2idx(r,c,shape):
264 """ 265 Given a row, column, and matrix shape, return the corresponding index 266 into the flattened (raveled) matrix. 267 """ 268 assert len(shape) == 2 269 rows,cols = shape 270 271 return r * cols + c
272 273 274
275 -def centroid(pts,weights):
276 """ 277 Return the centroid of a weighted set of points as an array. 278 279 The pts argument should be an array of points, one per row, 280 and weights should be a vector of weights. 281 """ 282 # CEBALERT: use numpy.sum? Worthwhile if weights is a numpy.array. 283 return numpy.dot(numpy.transpose(pts),weights)/sum(weights)
284 285 286
287 -def signabs(x):
288 """ 289 Split x into its sign and absolute value. 290 291 Returns a tuple (sign(x),abs(x)). Note: sign(0) = 1, unlike 292 numpy.sign. 293 """ 294 295 if x < 0: 296 sgn = -1 297 else: 298 sgn = 1 299 300 return sgn,abs(x)
301 302
303 -def linearly_interpolate(table,value):
304 """ 305 Interpolate an appropriate value from the given list of values, by number. 306 307 Assumes the table is a list of items to be returned for integer values, 308 and interpolates between those items for non-integer values. 309 """ 310 311 lower_index=int(value) 312 upper_index=lower_index+1 313 314 # Intermediate value; interpolate or return exact value as appropriate 315 if lower_index+1<len(table): 316 lookup=table[lower_index]+(value%1.0)*(table[upper_index]-table[lower_index]) 317 318 # Upper bound -- return largest value 319 elif lower_index+1==len(table): 320 lookup=table[len(table)-1] 321 322 # Over upper bound -- return largest value and print warning 323 # JABALERT: Printing a warning message is not necessarily the most 324 # useful behavior. Should at least provide some identification of 325 # where the warning is coming from. Should add bounds_error and/or 326 # bounds_warn options. (Could turn into a ParameterizedFunction so 327 # that any message includes an identification, and so that 328 # warnings-as-errors will work.) Would be nice if we could use the 329 # equivalent function from scipy, but we can't yet depend on scipy 330 # being available. 331 else: 332 lookup=table[len(table)-1] 333 print "Warning -- value %f out of range; returning maximum of %f" % (value,lookup) 334 335 return lookup
336 337 338 339 # CB: note that this has only really been tested for output; 340 # I've never tried using it to e.g. read multiple files.
341 -class MultiFile(object):
342 """ 343 For all file_like_objs passed on initialization, provides a 344 convenient way to call any of file's methods (on all of them). 345 346 E.g. The following would cause 'test' to be written into two 347 files, as well as to stdout: 348 349 import sys 350 f1 = open('file1','w') 351 f2 = open('file2','w') 352 m = MultiFile(f1,f2,sys.stdout) 353 m.write('test') 354 """
355 - def __init__(self,*file_like_objs):
356 self.file_like_objs=file_like_objs 357 self.__provide_file_methods()
358
359 - def __provide_file_methods(self):
360 # Provide a version of all the methods of the type file that 361 # don't start with '_'. In each case, the provided version 362 # loops over all the file_like_objs, calling the file method 363 # on all of them. 364 file_methods = [attr for attr in file.__dict__ 365 if not attr.startswith('_') 366 and callable(file.__dict__[attr])] 367 368 for method in file_methods: 369 def looped_method(method_,*args,**kw): 370 all_out = [] 371 for output in self.file_like_objs: 372 out = getattr(output,method_)(*args,**kw) 373 all_out.append(out) 374 return all_out
375 376 setattr(self,method,functools.partial(looped_method,method))
377 378 379 380 381 # CEBALERT: should be moved to legacy, once that file is reorganized 382 # so that classes can be imported from it relatively independently, 383 # without requiring all the legacy functions to be loaded. 384 # (Right now, topo.__init__ can't import gmpyFaker from 385 # topo.misc.legacy because importing topo.misc.legacy causes various 386 # pieces of code to run that depend on topo.sim existing.) 387 388 ############################################################ 389 # Alternative module faking using import hooks (see 390 # http://www.python.org/dev/peps/pep-0302/). 391 # Based on http://orestis.gr/blog/2008/12/20/python-import-hooks/. 392 import os,sys,imp
393 -class ModuleFaker(object):
394 - def load_module(self,name):
395 if name not in sys.modules: 396 module = self.create_module(name) 397 module.__file__ = self.path 398 sys.modules[name] = module 399 if '.' in name: 400 parent_name, child_name = name.rsplit('.', 1) 401 setattr(sys.modules[parent_name], child_name, module) 402 return sys.modules[name]
403
404 - def create_module(self,name):
405 raise NotImplementedError
406
407 -class ModuleImporter(object):
408 - def find_module(self,fullname,path=None):
409 raise NotImplementedError
410
411 -class gmpyFaker(ModuleFaker):
412 - def create_module(self,name):
413 module = imp.new_module(name) 414 # CEBALERT: not sure what precision should be used for FixedPoint to 415 # replace rational. Should we set the precision really high? 416 code = \ 417 """ 418 from __future__ import division 419 import topo.misc.fixedpoint as fixedpoint 420 import param 421 class mpq(object): 422 def __new__(self,*args,**kw): 423 n = fixedpoint.FixedPoint(eval(str(args[0])),precision=4) 424 param.Parameterized().warning("gmpy.mpq('%s') replaced by fixedpoint.FixedPoint('%s')"%(args[0],n)) 425 return n 426 """ 427 exec code in module.__dict__ 428 return module
429
430 -class gmpyImporter(ModuleImporter):
431
432 - def find_module(self, fullname, path=None):
433 if fullname == 'gmpy' or fullname.startswith('gmpy.'): 434 import param 435 param.Parameterized().warning('Module "gmpy" is not available. gmpy.mpq is provided by using fixedpoint.FixedPoint.') 436 g = gmpyFaker() 437 g.path = path 438 return g 439 return None
440