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