# -*- coding: utf-8 -*-
"""
Various types used in ObsPy.
:copyright:
The ObsPy Development Team (devs@obspy.org)
:license:
GNU Lesser General Public License, Version 3
(https://www.gnu.org/copyleft/lesser.html)
"""
from collections import OrderedDict
try:
import __builtin__
list = __builtin__.list
except ImportError:
pass
[docs]
class Enum(object):
"""
Enumerated type (enum) implementation for Python.
:type enums: list[str]
:type replace: dict, optional
:param replace: Dictionary of keys which are replaced by values.
.. rubric:: Example
>>> from obspy.core.util import Enum
>>> units = Enum(["m", "s", "m/s", "m/(s*s)", "m*s", "other"])
There are different ways to access the correct enum values:
>>> print(units.get('m/s'))
m/s
>>> print(units['S'])
s
>>> print(units.OTHER)
other
>>> print(units[3])
m/(s*s)
>>> units.xxx # doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: 'xxx'
Changing enum values will not work:
>>> units.m = 5 # doctest: +ELLIPSIS
Traceback (most recent call last):
...
NotImplementedError
>>> units['m'] = 'xxx' # doctest: +ELLIPSIS
Traceback (most recent call last):
...
NotImplementedError
Calling with a value will either return the mapped enum value or ``None``:
>>> print(units("M*s"))
m*s
>>> units('xxx')
>>> print(units(5))
other
The following enum allows replacing certain entries:
>>> units2 = Enum(["m", "s", "m/s", "m/(s*s)", "m*s", "other"],
... replace={'meter': 'm'})
>>> print(units2('m'))
m
>>> print(units2('meter'))
m
"""
# marker needed for for usage within ABC classes
__isabstractmethod__ = False
[docs]
def __init__(self, enums, replace={}):
self.__enums = OrderedDict((str(e).lower(), e) for e in enums)
self.__replace = replace
[docs]
def __call__(self, enum):
try:
return self.get(enum)
except Exception:
return None
[docs]
def get(self, key):
if isinstance(key, int):
return list(self.__enums.values())[key]
if key in self._Enum__replace:
return self._Enum__replace[key.lower()]
return self.__enums.__getitem__(key.lower())
[docs]
def __getattr__(self, name):
try:
return self.get(name)
except KeyError:
raise AttributeError("'%s'" % (name, ))
[docs]
def __setattr__(self, name, value):
if name == '_Enum__enums':
self.__dict__[name] = value
return
elif name == '_Enum__replace':
super(Enum, self).__setattr__(name, value)
return
raise NotImplementedError
__getitem__ = get
__setitem__ = __setattr__
[docs]
def __contains__(self, value):
return value.lower() in self.__enums
[docs]
def values(self):
return list(self.__enums.values())
[docs]
def keys(self):
return list(self.__enums.keys())
[docs]
def items(self):
return list(self.__enums.items())
[docs]
def iteritems(self):
return iter(self.__enums.items())
[docs]
def __str__(self):
"""
>>> enum = Enum(["c", "a", "b"])
>>> print(enum)
Enum(["c", "a", "b"])
>>> enum = Enum(["not existing",
... "not reported",
... "earthquake",
... "controlled explosion",
... "experimental explosion",
... "industrial explosion"])
>>> print(enum) # doctest: +NORMALIZE_WHITESPACE
Enum(["not existing", "not reported", ..., "experimental explosion",
"industrial explosion"])
"""
return self.__repr__()
[docs]
def __repr__(self):
"""
>>> enum = Enum(["c", "a", "b"])
>>> print(repr(enum))
Enum(["c", "a", "b"])
>>> enum = Enum(["not existing",
... "not reported",
... "earthquake",
... "controlled explosion",
... "experimental explosion",
... "industrial explosion"])
>>> print(repr(enum)) # doctest: +NORMALIZE_WHITESPACE
Enum(["not existing", "not reported", ..., "experimental explosion",
"industrial explosion"])
"""
def _repr_list_of_keys(keys):
return ", ".join('"{}"'.format(_i) for _i in keys)
keys = list(self.__enums.keys())
key_repr = _repr_list_of_keys(keys)
index = int(len(keys))
while len(key_repr) > 100:
if index == 0:
key_repr = "..."
break
index -= 1
key_repr = (_repr_list_of_keys(keys[:index]) + ", ..., " +
_repr_list_of_keys(keys[-index:]))
return "Enum([{}])".format(key_repr)
[docs]
def _repr_pretty_(self, p, cycle):
p.text(str(self))
[docs]
class CustomComplex(complex):
"""
Helper class to inherit from and which stores a complex number that is
extendable.
"""
[docs]
def __new__(cls, *args):
return super(CustomComplex, cls).__new__(cls, *args)
[docs]
def __init__(self, *args):
pass
[docs]
def __iadd__(self, other):
new = self.__class__(complex(self) + other)
new.__dict__.update(**self.__dict__)
self = new
[docs]
def __imul__(self, other):
new = self.__class__(complex(self) * other)
new.__dict__.update(**self.__dict__)
self = new
[docs]
class CustomFloat(float):
"""
Helper class to inherit from and which stores a float number that is
extendable.
"""
[docs]
def __new__(cls, *args):
return super(CustomFloat, cls).__new__(cls, *args)
[docs]
def __init__(self, *args):
pass
[docs]
def __iadd__(self, other):
new = self.__class__(float(self) + other)
new.__dict__.update(**self.__dict__)
self = new
[docs]
def __imul__(self, other):
new = self.__class__(float(self) * other)
new.__dict__.update(**self.__dict__)
self = new
[docs]
class FloatWithUncertainties(CustomFloat):
"""
Helper class to inherit from and which stores a float with a given valid
range, upper/lower uncertainties and eventual additional attributes.
"""
_minimum = float("-inf")
_maximum = float("inf")
[docs]
def __new__(cls, value, **kwargs):
if not cls._minimum <= float(value) <= cls._maximum:
msg = "value %s out of bounds (%s, %s)"
msg = msg % (value, cls._minimum, cls._maximum)
raise ValueError(msg)
return super(FloatWithUncertainties, cls).__new__(cls, value)
[docs]
def __init__(self, value, lower_uncertainty=None, upper_uncertainty=None,
measurement_method=None):
# set uncertainties, if initialized with similar type
if isinstance(value, FloatWithUncertainties):
if lower_uncertainty is None:
lower_uncertainty = value.lower_uncertainty
if upper_uncertainty is None:
upper_uncertainty = value.upper_uncertainty
# set/override uncertainties, if explicitly specified
self.lower_uncertainty = lower_uncertainty
self.upper_uncertainty = upper_uncertainty
self.measurement_method = measurement_method
[docs]
class FloatWithUncertaintiesFixedUnit(FloatWithUncertainties):
"""
Float value that has lower and upper uncertainties and a fixed unit
associated with it. Helper class to inherit from setting a custom value for
the fixed unit (set unit in derived class as class attribute).
:type value: float
:param value: Actual float value.
:type lower_uncertainty: float
:param lower_uncertainty: Lower uncertainty (aka minusError)
:type upper_uncertainty: float
:param upper_uncertainty: Upper uncertainty (aka plusError)
:type unit: str
:param unit: Unit for physical interpretation of the float value
(read only).
:type measurement_method: str
:param measurement_method: Method used in the measurement.
"""
_unit = ""
[docs]
def __init__(self, value, lower_uncertainty=None, upper_uncertainty=None,
measurement_method=None):
super(FloatWithUncertaintiesFixedUnit, self).__init__(
value, lower_uncertainty=lower_uncertainty,
upper_uncertainty=upper_uncertainty,
measurement_method=measurement_method)
@property
def unit(self):
return self._unit
@unit.setter
def unit(self, value):
msg = "Unit is fixed for this object class."
raise ValueError(msg)
[docs]
class FloatWithUncertaintiesAndUnit(FloatWithUncertainties):
"""
Float value that has lower and upper uncertainties and a unit associated
with it.
:type value: float
:param value: Actual float value.
:type lower_uncertainty: float
:param lower_uncertainty: Lower uncertainty (aka minusError)
:type upper_uncertainty: float
:param upper_uncertainty: Upper uncertainty (aka plusError)
:type unit: str
:param unit: Unit for physical interpretation of the float value.
:type measurement_method: str
:param measurement_method: Method used in the measurement.
"""
[docs]
def __init__(self, value, lower_uncertainty=None, upper_uncertainty=None,
unit=None, measurement_method=None):
super(FloatWithUncertaintiesAndUnit, self).__init__(
value, lower_uncertainty=lower_uncertainty,
upper_uncertainty=upper_uncertainty,
measurement_method=measurement_method)
if unit is None and hasattr(value, "unit"):
unit = value.unit
self.unit = unit
@property
def unit(self):
return self._unit
@unit.setter
def unit(self, value):
self._unit = value
class _ComplexUncertainty(complex):
"""
Complex class which can accept a python None as an argument and map it to
a float value for storage.
"""
_none = float("-inf")
@classmethod
def _encode(cls, arg):
if arg is None:
return cls._none
return arg
@classmethod
def _decode(cls, arg):
if arg == cls._none:
return None
return arg
def __new__(cls, *args):
cargs = [cls._encode(a) for a in args]
if len(args) < 1:
cargs.append(cls._none)
if len(args) < 2:
if args[0] is None:
cargs.append(cls._none)
else:
cargs.append(0)
return super(_ComplexUncertainty, cls).__new__(cls, *cargs)
@property
def real(self):
_real = super(_ComplexUncertainty, self).real
return self._decode(_real)
@property
def imag(self):
_imag = super(_ComplexUncertainty, self).imag
return self._decode(_imag)
[docs]
class ComplexWithUncertainties(CustomComplex):
"""
Complex class which can store uncertainties.
Accepts FloatWithUncertainties and returns FloatWithUncertainties from
property methods.
"""
_lower_uncertainty = None
_upper_uncertainty = None
[docs]
@staticmethod
def _attr(obj, attr):
try:
return getattr(obj, attr)
except AttributeError:
return None
[docs]
@staticmethod
def _uncertainty(value):
if isinstance(value, tuple) or isinstance(value, list):
u = _ComplexUncertainty(*value)
else:
u = _ComplexUncertainty(value)
if u.real is None and u.imag is None:
return None
return u
@property
def lower_uncertainty(self):
return self._lower_uncertainty
@lower_uncertainty.setter
def lower_uncertainty(self, value):
self._lower_uncertainty = self._uncertainty(value)
@property
def upper_uncertainty(self):
return self._upper_uncertainty
@upper_uncertainty.setter
def upper_uncertainty(self, value):
self._upper_uncertainty = self._uncertainty(value)
[docs]
def __new__(cls, *args, **kwargs):
return super(ComplexWithUncertainties, cls).__new__(cls, *args)
[docs]
def __init__(self, *args, **kwargs):
"""
Complex type with optional keywords:
:type lower_uncertainty: complex
:param lower_uncertainty: Lower uncertainty (aka minusError)
:type upper_uncertainty: complex
:param upper_uncertainty: Upper uncertainty (aka plusError)
:type measurement_method_real: str
:param measurement_method_real: Method used in the measurement of real
part.
:type measurement_method_imag: str
:param measurement_method_imag: Method used in the measurement of
imaginary part.
"""
real_upper = None
imag_upper = None
real_lower = None
imag_lower = None
if len(args) >= 1:
if isinstance(args[0], self.__class__):
self.upper_uncertainty = args[0].upper_uncertainty
self.lower_uncertainty = args[0].lower_uncertainty
elif isinstance(args[0], FloatWithUncertainties):
real_upper = args[0].upper_uncertainty
real_lower = args[0].lower_uncertainty
if len(args) >= 2 and isinstance(args[1], FloatWithUncertainties):
imag_upper = args[1].upper_uncertainty
imag_lower = args[1].lower_uncertainty
if self.upper_uncertainty is None:
self.upper_uncertainty = real_upper, imag_upper
if self.lower_uncertainty is None:
self.lower_uncertainty = real_lower, imag_lower
if "lower_uncertainty" in kwargs:
self.lower_uncertainty = kwargs['lower_uncertainty']
if "upper_uncertainty" in kwargs:
self.upper_uncertainty = kwargs['upper_uncertainty']
self.measurement_method_real = kwargs.get('measurement_method_real')
self.measurement_method_imag = kwargs.get('measurement_method_imag')
@property
def real(self):
_real = super(ComplexWithUncertainties, self).real
_lower = self._attr(self.lower_uncertainty, 'real')
_upper = self._attr(self.upper_uncertainty, 'real')
return FloatWithUncertainties(
_real, lower_uncertainty=_lower, upper_uncertainty=_upper,
measurement_method=self.measurement_method_real)
@property
def imag(self):
_imag = super(ComplexWithUncertainties, self).imag
_lower = self._attr(self.lower_uncertainty, 'imag')
_upper = self._attr(self.upper_uncertainty, 'imag')
return FloatWithUncertainties(
_imag, lower_uncertainty=_lower, upper_uncertainty=_upper,
measurement_method=self.measurement_method_imag)
[docs]
class ObsPyException(Exception):
pass
[docs]
class ObsPyReadingError(ObsPyException):
pass
[docs]
class ZeroSamplingRate(ObsPyException):
pass
if __name__ == '__main__':
import doctest
doctest.testmod(exclude_empty=True)