# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2012-2021 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.
"""
Module :mod:`openquake.hazardlib.imt` defines different intensity measure
types.
"""
import ast
import operator
import functools
import numpy
# NB: (MS) the management of the IMTs implemented here is complex, it would
# be better to have a single IMT class, but it is as it is for legacy reasons
registry = {}  # IMT string -> IMT class
[docs]def positivefloat(val):
    """
    Raise a ValueError if val <= 0
    """
    if val <= 0:
        raise ValueError(val) 
[docs]def imt2tup(string):
    """
    >>> imt2tup('PGA')
    ('PGA',)
    >>> imt2tup('SA(1.0)')
    ('SA', 1.0)
    >>> imt2tup('SA(1)')
    ('SA', 1.0)
    """
    s = string.strip()
    if not s.endswith(')'):
        # no parenthesis, PGA is considered the same as PGA()
        return (s,)
    name, rest = s.split('(', 1)
    return (name,) + tuple(float(x) for x in ast.literal_eval(rest[:-1] + ',')) 
[docs]def from_string(imt):
    """
    Convert an IMT string into an hazardlib object.
    :param str imt:
        Intensity Measure Type.
    """
    tup = imt2tup(imt)
    return registry[tup[0]](*tup[1:]) 
[docs]@functools.total_ordering
class IMT(tuple, metaclass=IMTMeta):
    """
    Base class for intensity measure type.
    Subclasses may define class attribute ``_fields`` as a tuple with names
    of parameters the specific intensity measure type requires (if there
    are any).
    """
    _fields = ()
    _defaults = None
    @property
    def name(self):
        """The name of the Intensity Measure Type (ex. "PGA", "SA", ...)"""
        return self[0]
    def __getnewargs__(self):
        return tuple(getattr(self, field) for field, check in self._fields)
    def __lt__(self, other):
        if not self._fields:
            return self[0] < other[0]  # ordered by name
        return (self[0], self[1] or 0, self[2] or 0) < (
            other[0], other[1] or 0, other[2] or 0)
    def __repr__(self):
        if not self._fields:  # return the name
            return self[0]
        return '%s(%s)' % (type(self).__name__,
                           ', '.join(str(getattr(self, field))
                                     for field, check in self._fields)) 
[docs]class PGA(IMT):
    """
    Peak ground acceleration during an earthquake measured in units
    of ``g``, times of gravitational acceleration.
    """
    period = 0.0 
[docs]class PGV(IMT):
    """
    Peak ground velocity during an earthquake measured in units of ``cm/sec``.
    """ 
[docs]class PGD(IMT):
    """
    Peak ground displacement during an earthquake measured in units of ``cm``.
    """ 
[docs]class SA(IMT):
    """
    Spectral acceleration, defined as the maximum acceleration of a damped,
    single-degree-of-freedom harmonic oscillator. Units are ``g``, times
    of gravitational acceleration.
    :param period:
        The natural period of the oscillator in seconds.
    :param damping:
        The degree of damping for the oscillator in percents.
    :raises ValueError:
        if period or damping is not positive.
    """
    _fields = (('period', positivefloat), ('damping', positivefloat))
    _defaults = (5.0,)  # damping
    def __repr__(self):
        if self.damping != 5.0:
            return '%s(%s, %s)' % (self.name, self.period, self.damping)
        else:
            return '%s(%s)' % (self.name, self.period) 
[docs]class AvgSA(IMT):
    """
    Dummy spectral acceleration to compute average ground motion over
    several spectral ordinates.
    """ 
[docs]class IA(IMT):
    """
    Arias intensity. Determines the intensity of shaking by measuring
    the acceleration of transient seismic waves. Units are ``m/s``.
    """ 
[docs]class CAV(IMT):
    """
    Cumulative Absolute Velocity. Defins the integral of the absolute
    acceleration time series. Units are "g-sec"
    """ 
[docs]class RSD(IMT):
    """
    Relative significant duration, 5-95% of :class:`Arias intensity
    <IA>`, in seconds.
    """ 
[docs]class RSD595(IMT):
    """
    Alias for RSD
    """ 
[docs]class RSD575(IMT):
    """
    Relative significant duration, 5-75% of :class:`Arias intensity
    <IA>`, in seconds.
    """ 
[docs]class RSD2080(IMT):
    """
    Relative significant duration, 20-80% of :class:`Arias intensity
    <IA>`, in seconds.
    """ 
[docs]class MMI(IMT):
    """
    Modified Mercalli intensity, a Roman numeral describing the severity
    of an earthquake in terms of its effects on the earth's surface
    and on humans and their structures.
    """ 
[docs]class JMA(IMT):
    """
    Modified Mercalli intensity, a Roman numeral describing the severity
    of an earthquake in terms of its effects on the earth's surface
    and on humans and their structures.
    """ 
# Volcanic IMTs
[docs]class ASH(IMT):
    """
    Level of the ash fall in millimeters
    """ 
# secondary perils
[docs]class Disp(IMT):
    """
    Displacement
    """ 
[docs]class DispProb(IMT):
    """
    Displacement probability
    """ 
[docs]class LiqProb(IMT):
    """
    Liquefaction probability
    """ 
[docs]class PGDMax(IMT):
    """
    Maximum between vert_settlement and lat_spread
    """
    def __call__(self, vert_settlement, lat_spread):
        return numpy.maximum(vert_settlement, lat_spread) 
[docs]class PGDGeomMean(IMT):
    """
    Geometric mean between vert_settlement and lat_spread
    """
    def __call__(cls, vert_settlement, lat_spread):
        return numpy.sqrt(vert_settlement * lat_spread)