# Copyright (C) 2002, 2003 by Intevation GmbH # Authors: # Thomas Arendsen Hein # # This program is free software under the GPL (>=v2) # Read the file COPYING coming with the software for details. """ Distribution class for Scientific Parameter """ __version__ = "$Revision: 1.14 $" # $Source: /greaterrepository/sciparam/SciParam/distribution.py,v $ # $Id: distribution.py,v 1.14 2003/09/29 16:39:19 thomas Exp $ import types from stochastic import LogNormToNorm, NormToLogNorm from math import exp class Distribution: """Distribution class References: Sachs L., Angewandte Statistik, Achte Auflage, Springer, Berlin (1997) """ types = ['none', 'normal', 'lognormal', 'uniform'] none = types[0] descriptives_map = { 'none': ('value',), 'normal': ('mean value', 'std. deviation'), 'lognormal': ('mean value', 'std. deviation'), 'uniform': ('min. value', 'max. value'), } def __init__(self, descriptives=None, type=None): if isinstance(descriptives, Distribution): self.type = descriptives.type self.descriptives = descriptives.descriptives else: self.type = type self.descriptives = descriptives def __str__(self): return "%s/%s" % (';'.join(map(str, self.descriptives)), self.type) def __setattr__(self, name, value): if name == 'descriptives': if type(value) == types.TupleType: if self.descriptives_len() == len(value): try: self.__dict__[name] = tuple(map(float, map(str, value))) except ValueError, why: self.__dict__[name] = self.descriptives_len() * (None,) else: raise ValueError, ("Illegal number of descriptives: %s" " (should be: %s)" % (len(value), self.descriptives_len())) elif type(value) == types.StringType: parts = value.split('/') if len(parts) == 2: value, self.type = parts elif len(parts) != 1: raise ValueError, "not a distribution: %s" % (value,) self.descriptives = tuple(value.split(';')) else: try: value = float(str(value)) except ValueError, why: value = None self.descriptives = ((value,) + (self.descriptives_len()-1) * (None,)) elif name == 'type': if value is None: value = self.none elif type(value) == types.StringType: value = value.strip().lower() if value not in self.types: raise ValueError, "not a distribution type: %s" % (value,) else: raise ValueError, "not a distribution type: %r" % (value,) old_type = self.__dict__.get(name) self.__dict__[name] = value if value != old_type: self.descriptives = None else: raise AttributeError, "%s has no attribute '%s'" % \ (self.__class__, name) def descriptives_len(self, type=None): """Return the number of descriptives for a given type.""" if type == None: type = self.type return len(self.descriptives_map[type]) def confidence_interval(self): """Return name/value pairs which should be inside this distribution. Format is a list of (name, value) tuples. """ z_normal = 3.29 # 99.9% confidence for normal distribution ci = [(self.descriptives_map[self.type][0], self.descriptives[0])] if self.type not in self.types: raise ValueError, "not a distribution type: %r" % (self.type,) elif self.type == 'none': pass elif self.type == 'normal': ci.append(('lower boundary of 99.9% confidence interval', self.descriptives[0] - self.descriptives[1]*z_normal)) ci.append(('upper boundary of 99.9% confidence interval', self.descriptives[0] + self.descriptives[1]*z_normal)) elif self.type == 'lognormal': dist = Distribution(LogNormToNorm(self.descriptives), 'normal') ci_lognormal = dist.confidence_interval() ci.append((ci_lognormal[1][0], exp(ci_lognormal[1][1]))) ci.append((ci_lognormal[2][0], exp(ci_lognormal[2][1]))) elif self.type == 'uniform': ci.append((self.descriptives_map[self.type][1], self.descriptives[1])) else: raise NotImplementedError, ("%s() not implemented for type: %s" % (__name__, self.type)) return ci def isvalid(self, errors=[]): """Are descriptives valid for this type of distribution?""" if self.type not in self.types: raise ValueError, "not a distribution type: %r" % (self.type,) elif self.type == 'none': valid = 1 elif self.type == 'normal': valid = self.descriptives[1] >= 0 if not valid: errors.append("%s has to be zero or greater." % (self.descriptives_map[self.type][1],)) elif self.type == 'lognormal': valid1 = self.descriptives[0] > 0 if not valid1: errors.append("%s has to be greater than zero." % (self.descriptives_map[self.type][0],)) valid2 = self.descriptives[1] >= 0 if not valid2: errors.append("%s has to be zero or greater." % (self.descriptives_map[self.type][1],)) valid = valid1 and valid2 elif self.type == 'uniform': valid = self.descriptives[0] <= self.descriptives[1] if not valid: errors.append("%s must not be greater than %s." % self.descriptives_map[self.type]) else: raise NotImplementedError, ("%s() not implemented for type: %s" % (__name__, self.type)) return valid def isusual(self, errors=[]): """Are descriptives usual for this type of distribution?""" if self.type not in self.types: raise ValueError, "not a distribution type: %r" % (self.type,) elif self.type == 'none': usual = 1 elif self.type == 'normal': usual = self.descriptives[1] > 0 if not usual: errors.append("%s should be greater than zero." % (self.descriptives_map[self.type][1],)) elif self.type == 'lognormal': usual1 = self.descriptives[0] > 0 if not usual1: errors.append("%s has to be greater than zero." % (self.descriptives_map[self.type][0],)) usual2 = self.descriptives[1] > 0 if not usual2: errors.append("%s should be greater than zero." % (self.descriptives_map[self.type][1],)) usual = usual1 and usual2 elif self.type == 'uniform': usual = self.descriptives[0] < self.descriptives[1] if not usual: errors.append("%s should be less than %s." % self.descriptives_map[self.type]) else: raise NotImplementedError, ("%s() not implemented for type: %s" % (__name__, self.type)) return usual if __name__ == "__main__": import os.path, sys print "Use test_%s to test this module." % os.path.basename(sys.argv[0])