Package topo :: Package numbergen :: Module basic
[hide private]
[frames] | no frames]

Source Code for Module topo.numbergen.basic

  1  """ 
  2  A collection of classes that, when called, generate numbers 
  3  according to different distributions (e.g. random numbers). 
  4   
  5  $Id: basic.py 10672 2009-10-28 01:00:50Z ceball $ 
  6  """ 
  7  __version__='$Revision: 8985 $' 
  8   
  9  import random 
 10  import operator 
 11   
 12  from math import e,pi 
 13   
 14  import param 
 15   
 16   
 17   
18 -class NumberGenerator(param.Parameterized):
19 """ 20 Abstract base class for any object that when called produces a number. 21 22 Primarily provides support for using NumberGenerators in simple 23 arithmetic expressions, such as abs((x+y)/z), where x,y,z are 24 NumberGenerators or numbers. 25 """ 26
27 - def __call__(self):
28 raise NotImplementedError
29 30 # Could define any of Python's operators here, esp. if they have operator or ufunc equivalents
31 - def __add__ (self,operand): return BinaryOperator(self,operand,operator.add)
32 - def __sub__ (self,operand): return BinaryOperator(self,operand,operator.sub)
33 - def __mul__ (self,operand): return BinaryOperator(self,operand,operator.mul)
34 - def __mod__ (self,operand): return BinaryOperator(self,operand,operator.mod)
35 - def __pow__ (self,operand): return BinaryOperator(self,operand,operator.pow)
36 - def __div__ (self,operand): return BinaryOperator(self,operand,operator.div)
37 - def __truediv__ (self,operand): return BinaryOperator(self,operand,operator.truediv)
38 - def __floordiv__ (self,operand): return BinaryOperator(self,operand,operator.floordiv)
39
40 - def __radd__ (self,operand): return BinaryOperator(self,operand,operator.add,True)
41 - def __rsub__ (self,operand): return BinaryOperator(self,operand,operator.sub,True)
42 - def __rmul__ (self,operand): return BinaryOperator(self,operand,operator.mul,True)
43 - def __rmod__ (self,operand): return BinaryOperator(self,operand,operator.mod,True)
44 - def __rpow__ (self,operand): return BinaryOperator(self,operand,operator.pow,True)
45 - def __rdiv__ (self,operand): return BinaryOperator(self,operand,operator.div,True)
46 - def __rtruediv__ (self,operand): return BinaryOperator(self,operand,operator.truediv,True)
47 - def __rfloordiv__(self,operand): return BinaryOperator(self,operand,operator.floordiv,True)
48
49 - def __neg__ (self): return UnaryOperator(self,operator.neg)
50 - def __pos__ (self): return UnaryOperator(self,operator.pos)
51 - def __abs__ (self): return UnaryOperator(self,operator.abs)
52 53 54
55 -class BinaryOperator(NumberGenerator):
56 """Applies any binary operator to NumberGenerators or numbers to yield a NumberGenerator.""" 57
58 - def __init__(self,lhs,rhs,operator,reverse=False,**args):
59 """ 60 Accepts two NumberGenerator operands, an operator, and 61 optional arguments to be provided to the operator when calling 62 it on the two operands. 63 """ 64 # Note that it's currently not possible to set 65 # parameters in the superclass when creating an instance, 66 # because **args is used by this class itself. 67 super(BinaryOperator,self).__init__() 68 69 if reverse: 70 self.lhs=rhs 71 self.rhs=lhs 72 else: 73 self.lhs=lhs 74 self.rhs=rhs 75 self.operator=operator 76 self.args=args
77
78 - def __call__(self):
79 return self.operator(self.lhs() if callable(self.lhs) else self.lhs, 80 self.rhs() if callable(self.rhs) else self.rhs, **self.args)
81 82 83
84 -class UnaryOperator(NumberGenerator):
85 """Applies any unary operator to a NumberGenerator to yield another NumberGenerator.""" 86
87 - def __init__(self,operand,operator,**args):
88 """ 89 Accepts a NumberGenerator operand, an operator, and 90 optional arguments to be provided to the operator when calling 91 it on the operand. 92 """ 93 # Note that it's currently not possible to set 94 # parameters in the superclass when creating an instance, 95 # because **args is used by this class itself. 96 super(UnaryOperator,self).__init__() 97 98 self.operand=operand 99 self.operator=operator 100 self.args=args
101
102 - def __call__(self):
103 return self.operator(self.operand(),**self.args)
104 105 106
107 -class RandomDistribution(NumberGenerator):
108 """ 109 Python's random module provides the Random class, which can be 110 instantiated to give an object that can be asked to generate 111 numbers from any of several different random distributions 112 (e.g. uniform, Gaussian). 113 114 To make it easier to use these, Topographica provides here a 115 hierarchy of classes, each tied to a particular random 116 distribution. This allows setting parameters on creation rather 117 than passing them each call, and allows pickling to work properly. 118 119 The underlying random.Random() instance and all its methods can be 120 accessed from the 'random_generator' attribute. 121 """ 122 __abstract = True 123
124 - def __init__(self,**params):
125 """ 126 Initialize a new Random() instance and store the supplied 127 positional and keyword arguments. 128 129 If seed=X is specified, sets the Random() instance's seed. 130 Otherwise, calls the instance's jumpahead() method to get a 131 state very likely to be different from any just used. 132 """ 133 self.random_generator = random.Random() 134 135 if 'seed' in params: 136 self.random_generator.seed(params['seed']) 137 del params['seed'] 138 else: 139 self.random_generator.jumpahead(10) 140 141 super(RandomDistribution,self).__init__(**params)
142
143 - def __call__(self):
144 raise NotImplementedError
145 146
147 -class UniformRandom(RandomDistribution):
148 """ 149 Specified with lbound and ubound; when called, return a random 150 number in the range [lbound, ubound). 151 152 See the random module for further details. 153 """ 154 lbound = param.Number(default=0.0,doc="inclusive lower bound") 155 ubound = param.Number(default=1.0,doc="exclusive upper bound") 156
157 - def __call__(self):
158 return self.random_generator.uniform(self.lbound,self.ubound)
159 160
161 -class UniformRandomInt(RandomDistribution):
162 """ 163 Specified with lbound and ubound; when called, return a random 164 number in the inclusive range [lbound, ubound]. 165 166 See the randint function in the random module for further details. 167 """ 168 lbound = param.Number(default=0,doc="inclusive lower bound") 169 ubound = param.Number(default=1000,doc="inclusive upper bound") 170
171 - def __call__(self):
172 x = self.random_generator.randint(self.lbound,self.ubound) 173 return x
174 175
176 -class Choice(RandomDistribution):
177 """ 178 Return a random element from the specified list of choices. 179 180 Accepts items of any type, though they are typically numbers. 181 See the choice() function in the random module for further details. 182 """ 183 choices = param.List(default=[0,1], 184 doc="List of items from which to select.") 185
186 - def __call__(self):
187 return self.random_generator.choice(self.choices)
188 189
190 -class NormalRandom(RandomDistribution):
191 """ 192 Normally distributed (Gaussian) random number. 193 194 Specified with mean mu and standard deviation sigma. 195 See the random module for further details. 196 """ 197 mu = param.Number(default=0.0,doc="Mean value.") 198 sigma = param.Number(default=1.0,doc="Standard deviation.") 199
200 - def __call__(self):
201 return self.random_generator.normalvariate(self.mu,self.sigma)
202 203
204 -class VonMisesRandom(RandomDistribution):
205 """ 206 Circularly normal distributed random number. 207 208 If kappa is zero, this distribution reduces to a uniform random 209 angle over the range 0 to 2*pi. Otherwise, it is concentrated to 210 a greater or lesser degree (determined by kappa) around the mean 211 mu. For large kappa (narrow peaks), this distribution approaches 212 the Gaussian (normal) distribution with variance 1/kappa. See the 213 random module for further details. 214 """ 215 216 mu = param.Number(default=0.0,softbounds=(0.0,2*pi),doc=""" 217 Mean value, in the range 0 to 2*pi.""") 218 219 kappa = param.Number(default=1.0,softbounds=(0.0,50.0),doc=""" 220 Concentration (inverse variance).""") 221
222 - def __call__(self):
223 return self.random_generator.vonmisesvariate(self.mu,self.kappa)
224 225 226 import topo
227 -class ExponentialDecay(NumberGenerator):
228 """ 229 Function object that provides a value that decays according to an 230 exponential function, based on topo.sim.time(). 231 232 Returns starting_value*base^(-time/time_constant). 233 234 See http://en.wikipedia.org/wiki/Exponential_decay. 235 """ 236 starting_value = param.Number(1.0, doc="Value used for time zero.") 237 ending_value = param.Number(0.0, doc="Value used for time infinity.") 238 239 time_constant = param.Number(10000,doc=""" 240 Time scale for the exponential; large values give slow decay.""") 241 242 base = param.Number(e, doc=""" 243 Base of the exponent; the default yields starting_value*exp(-t/time_constant). 244 Another popular choice of base is 2, which allows the 245 time_constant to be interpreted as a half-life.""") 246 247 # CEBALERT: default should be more like 'lambda:0', but that would 248 # confuse GUI users. 249 time_fn = param.Callable(default=topo.sim.time,doc=""" 250 Function to generate the time used for the decay.""") 251
252 - def __call__(self):
253 Vi = self.starting_value 254 Vm = self.ending_value 255 return Vm + (Vi - Vm) * self.base**(-1.0*float(self.time_fn())/ 256 float(self.time_constant))
257 258
259 -class BoundedNumber(NumberGenerator):
260 """ 261 Function object that silently enforces numeric bounds on values 262 returned by a callable object. 263 """ 264 generator = param.Callable(None, doc="Object to call to generate values.") 265 266 bounds = param.Parameter((None,None), doc=""" 267 Legal range for the value returned, as a pair. 268 269 The default bounds are (None,None), meaning there are actually 270 no bounds. One or both bounds can be set by specifying a 271 value. For instance, bounds=(None,10) means there is no lower 272 bound, and an upper bound of 10.""") 273
274 - def __call__(self):
275 val = self.generator() 276 min_, max_ = self.bounds 277 if min_ != None and val < min_: return min_ 278 elif max_ != None and val > max_: return max_ 279 else: return val
280 281 282 283 284 __all__ = list(set([k for k,v in locals().items() if isinstance(v,type) and issubclass(v,NumberGenerator)])) 285