Package csb :: Package statistics :: Package pdf :: Module parameterized
[frames] | no frames]

Source Code for Module csb.statistics.pdf.parameterized

  1  """ 
  2  Probability density functions with support for shared and computed parameters. 
  3   
  4  This module extends the functionality of L{csb.statistics.pdf} with a specialized  
  5  and more sophisticated L{AbstractDensity} -- the L{ParameterizedDensity}, which  
  6  works with L{AbstractParameter} objects rather than simple floats.  
  7   
  8  Each L{AbstractParameter} holds two properties - L{AbstractParameter.name} and 
  9  L{AbstractParameter.value}: 
 10   
 11      >>> class Sigma(AbstractParameter): 
 12      >>>     def _validate(self, value): 
 13      >>>         return float(value)      
 14      >>>     def _compute(self, base_value):                 
 15      >>>         return 1.0 / base_value ** 0.5 
 16      >>>                             
 17      >>> sigma = Sigma(3) 
 18      >>> sigma.name, sigma.value 
 19      sigma, 3 
 20   
 21  L{AbstractParameter}s holding a single float value are indistinguishable from  
 22  the simple float parameters used in L{csb.statistics.pdf.BaseDensity}.  
 23  However, each L{AbstractParameter} supports the concept of binding: 
 24   
 25      >>> sigma.is_virtual 
 26      False 
 27      >>> precision = Precision(1)  
 28      >>> sigma.bind_to(precision) 
 29      >>> sigma.is_virtual 
 30      True     
 31      >>> precision.set(2)  # triggers an implicit, lazy update in sigma 
 32      >>> sigma.set(1) 
 33      ParameterizationError: Virtual parameters can't be updated explicitly 
 34       
 35  The instance of Sigma is now a virtual parameter which receives automatic updates  
 36  from another base parameter using a predefined rule (L{AbstractParameter._compute}). 
 37  This is a lazy, on demand process. As soon as Sigma's computed value is  
 38  requested (via C{s.value}), Sigma will query the parameter it depends on  
 39  (Precision), which in turn will get recomputed first based on its own base, etc.  
 40  Thus, the L{AbstractParameter} model supports a parameter dependency chain with  
 41  linear or tree-like topologies:: 
 42               
 43                 sigma -- y    
 44                /               
 45      precision -- sigma2 -- x  
 46       
 47  In this graph precision is a base (non-virtual) parameter and sigma, sigma2, x, and y 
 48  are all virtual (computed). Binding precision to another parameter will immediately 
 49  turn it into a virtual one. However, cycles are not allowed (e.g. precision can't  
 50  be bound to sigma2 or x) and each virtual parameter must have exactly one base.    
 51   
 52  Computed parameters can then be used to implement custom PDFs with dependent  
 53  parameters within one PDF or spanning multiple PDFs. 
 54  """ 
 55   
 56  import csb.statistics.pdf as pdf 
 57   
 58  from abc import abstractmethod 
59 60 61 -class ParameterizationError(ValueError):
62 pass
63
64 -class ParameterValueError(pdf.ParameterValueError):
65 pass
66
67 68 -class ParameterizedDensity(pdf.AbstractDensity):
69 """ 70 Base abstract class for all PDFs, which operate on simple or computed 71 (chained) parameters. Parameters of type different from L{AbstractParameter} 72 will trigger TypeError-s. 73 """ 74
75 - def _set(self, param, value):
76 77 if not isinstance(value, AbstractParameter): 78 raise TypeError(value) 79 80 super(ParameterizedDensity, self)._set(param, value)
81
82 83 -class AbstractParameter(object):
84 """ 85 Abstract parameterization, which can exist independently or be coupled 86 to other parameters upon request. Virtual/coupled/derived parameters cannot 87 be overwritten explicitly, but their values will get recomputed once their 88 corresponding base parameters get updated. This is a lazy process - parameter 89 recalculation happens only when an out of date parameter is requested. This 90 triggers a real-time cascaded update which affects all parameters from the 91 nearest consistent base down to the current inconsistent node. 92 93 Implementing subclasses must override L{AbstractParameter._validate} 94 and virtual parameters should additionally override L{AbstractParameter._compute}. 95 96 @param value: initial value (defaults to None / AbstractParameter.NULL) 97 @type value: object 98 @param name: name of parameter (this is the name of the class by default) 99 @type name: str 100 @param base: optional base parameter to compute this instance from 101 @type base: L{AbstractParameter} 102 """ 103 104 NULL = None 105
106 - def __init__(self, value=NULL, name=None, base=None):
107 108 self._derivatives = set() 109 self._base = None 110 self._consistent = True 111 112 if name is None: 113 name = self.__class__.__name__.lower() 114 115 self._name = str(name) 116 self._value = AbstractParameter.NULL 117 118 self._update(value) 119 120 if base is not None: 121 self.bind_to(base)
122 123 @property
124 - def name(self):
125 """ 126 Parameter name 127 """ 128 return self._name
129 130 @property
131 - def value(self):
132 """ 133 Parameter value (guaranteed to be up to date) 134 """ 135 self._ensure_consistency() 136 return self._value
137 138 @property
139 - def is_virtual(self):
140 """ 141 True if this parameter is virtual (computed) 142 """ 143 return self._base is not None
144
145 - def set(self, value):
146 """ 147 Update the value of this parameter. This is not possible for 148 virtual parameters. 149 150 @param value: new value 151 @type value: object 152 153 @raise ParameterizationError: if this is a virtual parameter 154 @raise ParameterValueError: on invalid value 155 """ 156 if self.is_virtual: 157 raise ParameterizationError( 158 "Virtual parameters can't be updated explicitly") 159 160 self._update(value) 161 162 self._invalidate() 163 self._consistent = True
164
165 - def bind_to(self, parameter):
166 """ 167 Bind the current parameter to a base parameter. This converts 168 the current parameter to a virtual one, whose value will get 169 implicitly updated to be consistent with its base. 170 171 Note that virtual parameters must have exactly one base; computing a 172 parameter from multiple bases is not allowed. Cycles are also not 173 allowed; the topology must always stay a tree with a non-virtual 174 parameter at the root. 175 176 @param parameter: base parameter to compute this instance from 177 @param parameter: L{AbstractParameter} 178 179 @raise ParameterizationError: if this parameter is already virtual 180 @raise ParameterizationError: on attempt to create a circular dependency 181 182 """ 183 184 if not isinstance(parameter, AbstractParameter): 185 raise TypeError(parameter) 186 187 if parameter.find_base_parameter() is self: 188 raise ParameterizationError("Circular dependency detected") 189 190 if self.is_virtual: 191 msg = "Parameter {0.name} is already bound to {1.name}" 192 raise ParameterizationError(msg.format(self, self._base)) 193 194 self._set_base(parameter) 195 self._base._add_derived(self) 196 197 self._invalidate()
198
199 - def _set_base(self, parameter):
200 self._base = parameter
201
202 - def _add_derived(self, parameter):
203 self._derivatives.add(parameter)
204
205 - def _invalidate(self):
206 """ 207 Mark self and its virtual children as inconsistent 208 """ 209 for p in self._derivatives: 210 p._invalidate() 211 212 self._consistent = False
213
214 - def _update(self, value):
215 """ 216 Overwrite the current value of the parameter. This triggers 217 an abstract (custom) validation hook, but has no side effects 218 (i.e. it doesn't propagate!) 219 """ 220 sanitized = self._validate(value) 221 self._value = sanitized
222 223 @abstractmethod
224 - def _validate(self, value):
225 """ 226 Validate and sanitize the specified value before assignment. 227 @return: sanitized value 228 229 @raise ParameterValueError: on invalid value 230 """ 231 return value
232
233 - def _compute(self, base_value):
234 """ 235 Compute a new value for the current parameter given the value 236 of a base parameter (assuming self.is_virtual). By default this returns 237 the value of the base parameter (i.e. self just inherits the value 238 of its base untouched). 239 """ 240 return base_value
241
242 - def _ensure_consistency(self):
243 """ 244 Make sure that the current value is up to date. If it isn't, 245 trigger a real-time cascaded update following the path from the 246 nearest consistent base down to self. Also mark all nodes consistent 247 in the course of doing this update. 248 """ 249 if not self._consistent: 250 path = self._nearest_consistent_base() 251 252 for parameter in reversed(path): 253 parameter._recompute(consistent=True)
254
255 - def _recompute(self, consistent=True):
256 """ 257 If self is virtual, force the current parameter to recompute itself from 258 its immediate base. This operation has no side effects and does not 259 propagate. 260 """ 261 if self.is_virtual: 262 value = self._compute(self._base._value) 263 self._update(value) 264 265 if consistent: 266 self._consistent = True
267
268 - def _recompute_derivatives(self):
269 """ 270 Recompute all derived parameters starting from self and mark 271 them consistent. 272 """ 273 self._recompute(consistent=True) 274 275 for p in self._derivatives: 276 p._recompute_derivatives()
277
278 - def _nearest_consistent_base(self):
279 """ 280 Compute and return the path from self to the nearest consistent 281 base parameter. 282 283 @return: path, leaf-to-root 284 @rtype: list of L{AbstractParameter} 285 """ 286 root = self 287 path = [self] 288 289 while not root._consistent: 290 root = root._base 291 path.append(root) 292 293 return path
294
295 - def find_base_parameter(self):
296 """ 297 Find and return the non-virtual base parameter that is the root 298 of the current hierarchy. If self is not virtual, return self. 299 300 @return: base parameter 301 @rtype: L{AbstractParameter} 302 """ 303 root = self 304 305 while root.is_virtual: 306 root = root._base 307 308 return root
309
310 311 -class Parameter(AbstractParameter):
312 """ 313 Default parameter implementation which accepts float values only. 314 """ 315
316 - def __init__(self, value=0.0, name=None, base=None):
317 super(Parameter, self).__init__(value, name, base)
318
319 - def _validate(self, value):
320 321 try: 322 return float(value) 323 except (ValueError, TypeError): 324 raise ParameterValueError(self.name, value)
325
326 327 -class NonVirtualParameter(Parameter):
328 """ 329 A float L{Parameter} that is explicitly non-computed and cannot be 330 bound to another L{Parameter}. 331 """ 332
333 - def bind_to(self, parameter):
334 raise ParameterizationError( 335 "This parameter is explicitly non-computed")
336 337 @property
338 - def is_virtual(self):
339 return False
340