Source code for fdhpy.youngs_et_al_2003

"""Youngs et al. (2003) fault displacement model (https://doi.org/10.1193/1.1542891)."""

import logging
from typing import Dict, Optional, Union

import numpy as np
from scipy import stats
from scipy.stats._distn_infrastructure import rv_continuous

from fdhpy.cli import cli_runner
from fdhpy.fault_displacement_model import FaultDisplacementModel
from fdhpy.normalized_fault_displacement_model import NormalizedFaultDisplacementModel


[docs]class YoungsEtAl2003(FaultDisplacementModel): """ Youngs et al. (2003) fault displacement model. Applicable to principal surface fault displacement on normal faults. This is a normalized model (i.e., uses the D/AD or D/MD form). The reference displacement models (AD or MD) from Wells and Coppersmith (1994) for "All" styles are used. All calculations include total aleatory variability; in other words, the statistical distributions for D/AD and AD (or D/MD and MD) are convolved. Parameters ---------- style : str, optional Style of faulting (case-insensitive). Default is "normal". magnitude : float Earthquake moment magnitude. Recommended range is (5.5, 8). xl : float, optional Normalized location x/L along the rupture length, range [0, 1.0]. xl_step : float, optional Step size increment for slip profile calculations. Default is 0.05. percentile : float, optional Aleatory quantile of interest. Use -1 for mean. metric : str, optional Definition of displacement (case-insensitive). Valid options are "principal". Default is "principal". version : str Name of the model formulation for the given metric (case-insensitive). Valid options are "d/ad" or "d/md". displ_array : np.ndarray, optional Displacement test value(s) in meters. Default array is provided. Notes ----- - Distributed displacement models are not implemented yet. See model help at the module level: .. code-block:: python from fdhpy import YoungsEtAl2003 print(YoungsEtAl2003.__doc__) See model help in command line: .. code-block:: console $ fd-yea03 --help """ _CONDITIONS = { "style": { "normal": {}, # Magnitude range not provided by model; default in parent class is used }, "metric": { "principal": {"version": ("d/ad", "d/md")}, "distributed": {"version": None}, }, } _MODEL_NAME = "YoungsEtAl2003" # Override the init method to set model defaults def __init__(self, **kwargs): kwargs.setdefault("metric", "principal") kwargs.setdefault("style", "normal") super().__init__(**kwargs) # NOTE: magnitude, xl, and version are validated in parent class initialization # Necessary optional methods in FaultDisplacementModel parent class @property def _folded_xl(self) -> Optional[float]: return self._calc_folded_xl() # Required methods for implementing normalized fault displacement models @property def _AD_MAG_SCALE_PARAMS(self): """Set parameters for loglinear magnitude scaling for average displacement.""" # Parameters based on Wells and Coppersmith (1994) All styles return {"intercept": -4.8, "slope": 0.69, "std_dev": 0.36} @property def _MD_MAG_SCALE_PARAMS(self): """Set parameters for loglinear magnitude scaling for maximum displacement.""" # Parameters based on Wells and Coppersmith (1994) All styles return {"intercept": -5.46, "slope": 0.82, "std_dev": 0.42} @property def _normalized_calcs(self) -> NormalizedFaultDisplacementModel: """Return an instance of the NormalizedFaultDisplacementModel for the current context.""" return NormalizedFaultDisplacementModel(self) # Mandated methods in FaultDisplacementModel parent class def _statistical_distribution_params(self) -> None: """ Compute and set the statistical distribution parameters mu, sigma, alpha, and beta for the model version (i.e., "d/ad" or "d/md"). """ if self.metric == "distributed": e = NotImplementedError( f"Distributed model(s) are not implemented yet in `{self._MODEL_NAME}`." ) logging.error(e) return if self.xl is not None: if self.version == "d/ad": a1, a2 = 1.628, -0.193 b1, b2 = -0.476, 0.009 elif self.version == "d/md": a1, a2 = 1.138, -0.705 b1, b2 = -0.257, 0.421 self._alpha = float(np.exp(a1 * self._folded_xl + a2)) self._beta = float(np.exp(b1 * self._folded_xl + b2)) # Compute mu and sigma parameters based on magnitude and model version regr_params_map = { "d/ad": self._AD_MAG_SCALE_PARAMS, "d/md": self._MD_MAG_SCALE_PARAMS, } if self.magnitude is not None: # NOTE: mu based on magnitude is handled in self._normalized_calcs p = self._normalized_calcs._calc_mag_scale_stat_params(regr_params_map[self.version]) self._mu, self._sigma = p[0], p[1] # Set to None if not computed self._alpha = getattr(self, "_alpha", None) self._beta = getattr(self, "_beta", None) self._mu = getattr(self, "_mu", None) self._sigma = getattr(self, "_sigma", None) def _calc_displ_site(self) -> Optional[float]: """Calculate deterministic scenario displacement.""" return self._normalized_calcs._site_displ() def _calc_displ_avg(self) -> Optional[float]: """Calculate average displacement.""" if self.metric == "distributed": e = ValueError( "Average displacement cannot be computed for distributed faults. Use " "`metric='principal'` instead." ) logging.error(e) return return self._normalized_calcs._avg_displ(self._AD_MAG_SCALE_PARAMS) def _calc_displ_max(self) -> Optional[float]: """Calculate maximum displacement.""" if self.metric == "distributed": e = ValueError( "Maximum displacement cannot be computed for distributed faults. Use " "`metric='principal'` instead." ) logging.error(e) return return self._normalized_calcs._max_displ(self._MD_MAG_SCALE_PARAMS) def _calc_cdf(self) -> Optional[np.ndarray]: """Calculate cumulative probability.""" return self._normalized_calcs._cdf() # Ensure statistical distribution parameters are updated for current instance @property def stat_params_info( self, ) -> Dict[str, Union[Dict[str, Optional[float]], rv_continuous, Dict[str, float]]]: """ Dictionary of statistical parameters ("params"), probability distribution ("prob_distribution"), and its arguments ("prob_distribution_kwargs") for the instance. """ self._statistical_distribution_params() statistical_parameters = { "mu": self._mu, "sigma": self._sigma, "alpha": self._alpha, "beta": self._beta, } # Use nomenclature in Youngs et al. (2003) if self.version == "d/ad": probability_distribution = stats.gamma probability_distribution_kwargs = { "a": statistical_parameters["alpha"], "loc": 0, "scale": statistical_parameters["beta"], } elif self.version == "d/md": probability_distribution = stats.beta probability_distribution_kwargs = { "a": statistical_parameters["alpha"], "b": statistical_parameters["beta"], } return { "params": statistical_parameters, "prob_distribution": probability_distribution, "prob_distribution_kwargs": probability_distribution_kwargs, } @staticmethod def main(): cli_runner(YoungsEtAl2003)
# Inherit the docstrings from parent class YoungsEtAl2003._calc_displ_site.__doc__ = FaultDisplacementModel._calc_displ_site.__doc__ YoungsEtAl2003._calc_displ_avg.__doc__ = FaultDisplacementModel._calc_displ_avg.__doc__ YoungsEtAl2003.displ_max.__doc__ = FaultDisplacementModel.displ_max.__doc__ YoungsEtAl2003.displ_profile.__doc__ = FaultDisplacementModel.displ_profile.__doc__ YoungsEtAl2003.cdf.__doc__ = FaultDisplacementModel.cdf.__doc__ YoungsEtAl2003.prob_exceed.__doc__ = FaultDisplacementModel.prob_exceed.__doc__ if __name__ == "__main__": YoungsEtAl2003.main()