1# -*- coding: utf-8 -*- 2""" 3Various types used in ObsPy. 4 5:copyright: 6 The ObsPy Development Team (devs@obspy.org) 7:license: 8 GNU Lesser General Public License, Version 3 9 (https://www.gnu.org/copyleft/lesser.html) 10""" 11from __future__ import (absolute_import, division, print_function, 12 unicode_literals) 13from future.builtins import * # NOQA 14 15from collections import OrderedDict 16 17try: 18 import __builtin__ 19 list = __builtin__.list 20except ImportError: 21 pass 22 23 24class Enum(object): 25 """ 26 Enumerated type (enum) implementation for Python. 27 28 :type enums: list of str 29 :type replace: dict, optional 30 :param replace: Dictionary of keys which are replaced by values. 31 32 .. rubric:: Example 33 34 >>> from obspy.core.util import Enum 35 >>> units = Enum(["m", "s", "m/s", "m/(s*s)", "m*s", "other"]) 36 37 There are different ways to access the correct enum values: 38 39 >>> print(units.get('m/s')) 40 m/s 41 >>> print(units['S']) 42 s 43 >>> print(units.OTHER) 44 other 45 >>> print(units[3]) 46 m/(s*s) 47 >>> units.xxx # doctest: +ELLIPSIS 48 Traceback (most recent call last): 49 ... 50 AttributeError: 'xxx' 51 52 Changing enum values will not work: 53 54 >>> units.m = 5 # doctest: +ELLIPSIS 55 Traceback (most recent call last): 56 ... 57 NotImplementedError 58 >>> units['m'] = 'xxx' # doctest: +ELLIPSIS 59 Traceback (most recent call last): 60 ... 61 NotImplementedError 62 63 Calling with a value will either return the mapped enum value or ``None``: 64 65 >>> print(units("M*s")) 66 m*s 67 >>> units('xxx') 68 >>> print(units(5)) 69 other 70 71 The following enum allows replacing certain entries: 72 73 >>> units2 = Enum(["m", "s", "m/s", "m/(s*s)", "m*s", "other"], 74 ... replace={'meter': 'm'}) 75 >>> print(units2('m')) 76 m 77 >>> print(units2('meter')) 78 m 79 """ 80 # marker needed for for usage within ABC classes 81 __isabstractmethod__ = False 82 83 def __init__(self, enums, replace={}): 84 self.__enums = OrderedDict((str(e).lower(), e) for e in enums) 85 self.__replace = replace 86 87 def __call__(self, enum): 88 try: 89 return self.get(enum) 90 except Exception: 91 return None 92 93 def get(self, key): 94 if isinstance(key, int): 95 return list(self.__enums.values())[key] 96 if key in self._Enum__replace: 97 return self._Enum__replace[key.lower()] 98 return self.__enums.__getitem__(key.lower()) 99 100 def __getattr__(self, name): 101 try: 102 return self.get(name) 103 except KeyError: 104 raise AttributeError("'%s'" % (name, )) 105 106 def __setattr__(self, name, value): 107 if name == '_Enum__enums': 108 self.__dict__[name] = value 109 return 110 elif name == '_Enum__replace': 111 super(Enum, self).__setattr__(name, value) 112 return 113 raise NotImplementedError 114 115 __getitem__ = get 116 __setitem__ = __setattr__ 117 118 def __contains__(self, value): 119 return value.lower() in self.__enums 120 121 def values(self): 122 return list(self.__enums.values()) 123 124 def keys(self): 125 return list(self.__enums.keys()) 126 127 def items(self): 128 return list(self.__enums.items()) 129 130 def iteritems(self): 131 return iter(self.__enums.items()) 132 133 def __str__(self): 134 """ 135 >>> enum = Enum(["c", "a", "b"]) 136 >>> print(enum) 137 Enum(["c", "a", "b"]) 138 >>> enum = Enum(["not existing", 139 ... "not reported", 140 ... "earthquake", 141 ... "controlled explosion", 142 ... "experimental explosion", 143 ... "industrial explosion"]) 144 >>> print(enum) # doctest: +NORMALIZE_WHITESPACE 145 Enum(["not existing", "not reported", ..., "experimental explosion", 146 "industrial explosion"]) 147 """ 148 return self.__repr__() 149 150 def __repr__(self): 151 """ 152 >>> enum = Enum(["c", "a", "b"]) 153 >>> print(repr(enum)) 154 Enum(["c", "a", "b"]) 155 >>> enum = Enum(["not existing", 156 ... "not reported", 157 ... "earthquake", 158 ... "controlled explosion", 159 ... "experimental explosion", 160 ... "industrial explosion"]) 161 >>> print(repr(enum)) # doctest: +NORMALIZE_WHITESPACE 162 Enum(["not existing", "not reported", ..., "experimental explosion", 163 "industrial explosion"]) 164 """ 165 def _repr_list_of_keys(keys): 166 return ", ".join('"{}"'.format(_i) for _i in keys) 167 168 keys = list(self.__enums.keys()) 169 key_repr = _repr_list_of_keys(keys) 170 index = int(len(keys)) 171 while len(key_repr) > 100: 172 if index == 0: 173 key_repr = "..." 174 break 175 index -= 1 176 key_repr = (_repr_list_of_keys(keys[:index]) + ", ..., " + 177 _repr_list_of_keys(keys[-index:])) 178 return "Enum([{}])".format(key_repr) 179 180 def _repr_pretty_(self, p, cycle): 181 p.text(str(self)) 182 183 184class CustomComplex(complex): 185 """ 186 Helper class to inherit from and which stores a complex number that is 187 extendable. 188 """ 189 def __new__(cls, *args): 190 return super(CustomComplex, cls).__new__(cls, *args) 191 192 def __init__(self, *args): 193 pass 194 195 def __iadd__(self, other): 196 new = self.__class__(complex(self) + other) 197 new.__dict__.update(**self.__dict__) 198 self = new 199 200 def __imul__(self, other): 201 new = self.__class__(complex(self) * other) 202 new.__dict__.update(**self.__dict__) 203 self = new 204 205 206class CustomFloat(float): 207 """ 208 Helper class to inherit from and which stores a float number that is 209 extendable. 210 """ 211 def __new__(cls, *args): 212 return super(CustomFloat, cls).__new__(cls, *args) 213 214 def __init__(self, *args): 215 pass 216 217 def __iadd__(self, other): 218 new = self.__class__(float(self) + other) 219 new.__dict__.update(**self.__dict__) 220 self = new 221 222 def __imul__(self, other): 223 new = self.__class__(float(self) * other) 224 new.__dict__.update(**self.__dict__) 225 self = new 226 227 228class FloatWithUncertainties(CustomFloat): 229 """ 230 Helper class to inherit from and which stores a float with a given valid 231 range, upper/lower uncertainties and eventual additional attributes. 232 """ 233 _minimum = float("-inf") 234 _maximum = float("inf") 235 236 def __new__(cls, value, **kwargs): 237 if not cls._minimum <= float(value) <= cls._maximum: 238 msg = "value %s out of bounds (%s, %s)" 239 msg = msg % (value, cls._minimum, cls._maximum) 240 raise ValueError(msg) 241 return super(FloatWithUncertainties, cls).__new__(cls, value) 242 243 def __init__(self, value, lower_uncertainty=None, upper_uncertainty=None, 244 measurement_method=None): 245 # set uncertainties, if initialized with similar type 246 if isinstance(value, FloatWithUncertainties): 247 if lower_uncertainty is None: 248 lower_uncertainty = value.lower_uncertainty 249 if upper_uncertainty is None: 250 upper_uncertainty = value.upper_uncertainty 251 # set/override uncertainties, if explicitly specified 252 self.lower_uncertainty = lower_uncertainty 253 self.upper_uncertainty = upper_uncertainty 254 self.measurement_method = measurement_method 255 256 257class FloatWithUncertaintiesFixedUnit(FloatWithUncertainties): 258 """ 259 Float value that has lower and upper uncertainties and a fixed unit 260 associated with it. Helper class to inherit from setting a custom value for 261 the fixed unit (set unit in derived class as class attribute). 262 263 :type value: float 264 :param value: Actual float value. 265 :type lower_uncertainty: float 266 :param lower_uncertainty: Lower uncertainty (aka minusError) 267 :type upper_uncertainty: float 268 :param upper_uncertainty: Upper uncertainty (aka plusError) 269 :type unit: str (read only) 270 :param unit: Unit for physical interpretation of the float value. 271 :type measurement_method: str 272 :param measurement_method: Method used in the measurement. 273 """ 274 _unit = "" 275 276 def __init__(self, value, lower_uncertainty=None, upper_uncertainty=None, 277 measurement_method=None): 278 super(FloatWithUncertaintiesFixedUnit, self).__init__( 279 value, lower_uncertainty=lower_uncertainty, 280 upper_uncertainty=upper_uncertainty, 281 measurement_method=measurement_method) 282 283 @property 284 def unit(self): 285 return self._unit 286 287 @unit.setter 288 def unit(self, value): 289 msg = "Unit is fixed for this object class." 290 raise ValueError(msg) 291 292 293class FloatWithUncertaintiesAndUnit(FloatWithUncertainties): 294 """ 295 Float value that has lower and upper uncertainties and a unit associated 296 with it. 297 298 :type value: float 299 :param value: Actual float value. 300 :type lower_uncertainty: float 301 :param lower_uncertainty: Lower uncertainty (aka minusError) 302 :type upper_uncertainty: float 303 :param upper_uncertainty: Upper uncertainty (aka plusError) 304 :type unit: str 305 :param unit: Unit for physical interpretation of the float value. 306 :type measurement_method: str 307 :param measurement_method: Method used in the measurement. 308 """ 309 def __init__(self, value, lower_uncertainty=None, upper_uncertainty=None, 310 unit=None, measurement_method=None): 311 super(FloatWithUncertaintiesAndUnit, self).__init__( 312 value, lower_uncertainty=lower_uncertainty, 313 upper_uncertainty=upper_uncertainty, 314 measurement_method=measurement_method) 315 if unit is None and hasattr(value, "unit"): 316 unit = value.unit 317 self.unit = unit 318 319 @property 320 def unit(self): 321 return self._unit 322 323 @unit.setter 324 def unit(self, value): 325 self._unit = value 326 327 328class _ComplexUncertainty(complex): 329 """ 330 Complex class which can accept a python None as an argument and map it to 331 a float value for storage. 332 """ 333 _none = float("-inf") 334 335 @classmethod 336 def _encode(cls, arg): 337 if arg is None: 338 return cls._none 339 return arg 340 341 @classmethod 342 def _decode(cls, arg): 343 if arg == cls._none: 344 return None 345 return arg 346 347 def __new__(cls, *args): 348 cargs = [cls._encode(a) for a in args] 349 if len(args) < 1: 350 cargs.append(cls._none) 351 if len(args) < 2: 352 if args[0] is None: 353 cargs.append(cls._none) 354 else: 355 cargs.append(0) 356 return super(_ComplexUncertainty, cls).__new__(cls, *cargs) 357 358 @property 359 def real(self): 360 _real = super(_ComplexUncertainty, self).real 361 return self._decode(_real) 362 363 @property 364 def imag(self): 365 _imag = super(_ComplexUncertainty, self).imag 366 return self._decode(_imag) 367 368 369class ComplexWithUncertainties(CustomComplex): 370 """ 371 Complex class which can store uncertainties. 372 373 Accepts FloatWithUncertainties and returns FloatWithUncertainties from 374 property methods. 375 """ 376 _lower_uncertainty = None 377 _upper_uncertainty = None 378 379 @staticmethod 380 def _attr(obj, attr): 381 try: 382 return getattr(obj, attr) 383 except AttributeError: 384 return None 385 386 @staticmethod 387 def _uncertainty(value): 388 if isinstance(value, tuple) or isinstance(value, list): 389 u = _ComplexUncertainty(*value) 390 else: 391 u = _ComplexUncertainty(value) 392 if u.real is None and u.imag is None: 393 return None 394 return u 395 396 @property 397 def lower_uncertainty(self): 398 return self._lower_uncertainty 399 400 @lower_uncertainty.setter 401 def lower_uncertainty(self, value): 402 self._lower_uncertainty = self._uncertainty(value) 403 404 @property 405 def upper_uncertainty(self): 406 return self._upper_uncertainty 407 408 @upper_uncertainty.setter 409 def upper_uncertainty(self, value): 410 self._upper_uncertainty = self._uncertainty(value) 411 412 def __new__(cls, *args, **kwargs): 413 return super(ComplexWithUncertainties, cls).__new__(cls, *args) 414 415 def __init__(self, *args, **kwargs): 416 """ 417 Complex type with optional keywords: 418 419 :type lower_uncertainty: complex 420 :param lower_uncertainty: Lower uncertainty (aka minusError) 421 :type upper_uncertainty: complex 422 :param upper_uncertainty: Upper uncertainty (aka plusError) 423 :type measurement_method_real: str 424 :param measurement_method_real: Method used in the measurement of real 425 part. 426 :type measurement_method_imag: str 427 :param measurement_method_imag: Method used in the measurement of 428 imaginary part. 429 430 """ 431 real_upper = None 432 imag_upper = None 433 real_lower = None 434 imag_lower = None 435 if len(args) >= 1: 436 if isinstance(args[0], self.__class__): 437 self.upper_uncertainty = args[0].upper_uncertainty 438 self.lower_uncertainty = args[0].lower_uncertainty 439 elif isinstance(args[0], FloatWithUncertainties): 440 real_upper = args[0].upper_uncertainty 441 real_lower = args[0].lower_uncertainty 442 if len(args) >= 2 and isinstance(args[1], FloatWithUncertainties): 443 imag_upper = args[1].upper_uncertainty 444 imag_lower = args[1].lower_uncertainty 445 if self.upper_uncertainty is None: 446 self.upper_uncertainty = real_upper, imag_upper 447 if self.lower_uncertainty is None: 448 self.lower_uncertainty = real_lower, imag_lower 449 if "lower_uncertainty" in kwargs: 450 self.lower_uncertainty = kwargs['lower_uncertainty'] 451 if "upper_uncertainty" in kwargs: 452 self.upper_uncertainty = kwargs['upper_uncertainty'] 453 self.measurement_method_real = kwargs.get('measurement_method_real') 454 self.measurement_method_imag = kwargs.get('measurement_method_imag') 455 456 @property 457 def real(self): 458 _real = super(ComplexWithUncertainties, self).real 459 _lower = self._attr(self.lower_uncertainty, 'real') 460 _upper = self._attr(self.upper_uncertainty, 'real') 461 return FloatWithUncertainties( 462 _real, lower_uncertainty=_lower, upper_uncertainty=_upper, 463 measurement_method=self.measurement_method_real) 464 465 @property 466 def imag(self): 467 _imag = super(ComplexWithUncertainties, self).imag 468 _lower = self._attr(self.lower_uncertainty, 'imag') 469 _upper = self._attr(self.upper_uncertainty, 'imag') 470 return FloatWithUncertainties( 471 _imag, lower_uncertainty=_lower, upper_uncertainty=_upper, 472 measurement_method=self.measurement_method_imag) 473 474 475class ObsPyException(Exception): 476 pass 477 478 479class ObsPyReadingError(ObsPyException): 480 pass 481 482 483class ZeroSamplingRate(ObsPyException): 484 pass 485 486 487if __name__ == '__main__': 488 import doctest 489 doctest.testmod(exclude_empty=True) 490