Skip to content
8 changes: 8 additions & 0 deletions doc/api-reference/openquake.hazardlib.gsim.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ abrahamson_2018
:undoc-members:
:show-inheritance:

abrahamson_bhasin_2020
-----------------------------------------------

.. automodule:: openquake.hazardlib.gsim.abrahamson_bhasin_2020
:members:
:undoc-members:
:show-inheritance:

abrahamson_gulerce_2020
-----------------------------------------------

Expand Down
225 changes: 225 additions & 0 deletions openquake/hazardlib/gsim/abrahamson_bhasin_2020.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2025 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 exports: :class:`AbrahamsonBhasin2020`,
:class:`AbrahamsonBhasin2020PGA`,
:class:`AbrahamsonBhasin2020SA1`,
"""

import numpy as np
from openquake.hazardlib import const
from openquake.hazardlib.imt import SA, PGV, PGA
from openquake.hazardlib.gsim.base import GMPE, registry
from openquake.hazardlib.contexts import get_mean_stds

not_verified = True

# Abrahamson & Bhasin (2020) conditional PGV (horizontal component)
# coefficients (Table 3.2)
AB20_COEFFS = {
"general": {
"a1": 5.39, "a2": 0.799, "a3": 0.654, "a4": 0.479, "a5": -0.062,
"a6": -0.359, "a7": -0.134, "a8": 0.023, "phi": 0.29, "tau": 0.16,
"sigma": 0.33
},
"pga-based": {
"a1": 4.77, "a2": 0.738, "a3": 0.484, "a4": 0.275, "a5": -0.036,
"a6": -0.332, "a7": -0.44, "a8": 0.0, "phi_1": 0.32, "phi_2": 0.42,
"tau_1": 0.12, "tau_2": 0.26, "sigma_1": 0.34, "sigma_2": 0.49,
},
"sa1_based": {
"a1": 4.80, "a2": 0.82, "a3": 0.55, "a4": 0.27, "a5": -0.054,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"a5": -0.054 "a5": 0.054

"a6": -0.382, "a7": -0.21, "a8": 0.0, "phi_1": 0.28, "phi_2": 0.38,
"tau_1": 0.12, "tau_2": 0.17, "sigma_1": 0.30, "sigma_2": 0.42,
}
}


M1 = 5.0
M2 = 7.0


def _get_tref(mag):
"""Magnitude-dependent conditioning period Tref.
(see Table 3.1)

"""
for mm, tt in [(3.5, 0.20), (4.5, 0.28), (5.5, 0.40),
(6.5, 0.95), (7.5, 1.40), (8.5, 2.80)]:
if mag <= mm:
return tt
return 2.80


def _get_trilinear_magnitude_term(ctx: np.recarray, C: dict):
"""Magnitude-dependent slope term f1(M) of ln(PSA(tref)).
(see eq. 3.8)

"""
f1 = np.empty_like(ctx.mag, dtype=float)
m1 = (ctx.mag < 5.0)
m2 = (ctx.mag >= 5.0) & (ctx.mag <= 7.5)
m3 = ~(m1 | m2)
f1[m1] = C["a2"]
f1[m2] = C["a2"] + (C["a3"] - C["a2"]) * (ctx.mag[m2] - 5.0) / 2.5
f1[m3] = C["a3"]
return f1


def _tri_linear_stdev_term(M: np.ndarray, stdev1: float, stdev2: float):
"""Tri-linear interpolation between used in pga- and sa1-based
models.
(see eq. 3.9)

"""
out = np.empty_like(M, dtype=float)
m_lo = (M < M1)
m_md = (M >= M1) & (M <= M2)
m_hi = (M > M2)
out[m_lo] = stdev1
out[m_md] = stdev1 + (stdev2 - stdev1) * (M[m_md] - M1) / (M2 - M1)
out[m_hi] = stdev2
return out


def get_mean_conditional_pgv(
C: dict,
ctx: np.recarray,
mean_gms: dict,
imt_key: str
):

lnY = mean_gms[imt_key]
f1 = _get_trilinear_magnitude_term(ctx, C)
return (
C["a1"]
+ f1 * lnY
+ C["a4"] * (ctx.mag - 6.0)
+ C["a5"] * (8.5 - ctx.mag) ** 2
+ C["a6"] * np.log(ctx.rrup + 5.0 * np.exp(0.4 * (ctx.mag - 6.0)))
+ (C["a7"] + C["a8"] * (ctx.mag - 5.0)) * np.log(ctx.vs30 / 425.0)
)


def get_standard_deviations(
C: dict,
ctx: np.recarray,
sigma_gms: dict,
tau_gms: dict,
phi_gms: dict,
imt_key: str):

f1 = _get_trilinear_magnitude_term(ctx, C)
sigma_cond = sigma_gms[imt_key]
if "sigma" in C: # general model
sigma_pgv = np.full_like(sigma_cond, C["sigma"], dtype=float)
tau_pgv = np.full_like(sigma_cond, C["tau"], dtype=float)
phi_pgv = np.full_like(sigma_cond, C["phi"], dtype=float)
else: # fixed-IMT variants
M = np.asarray(ctx.mag, dtype=float)
sigma_pgv = _tri_linear_stdev_term(M, C["sigma_1"], C["sigma_2"])
tau_pgv = _tri_linear_stdev_term(M, C["tau_1"], C["tau_2"])
phi_pgv = _tri_linear_stdev_term(M, C["phi_1"], C["phi_2"])

tau_cond = tau_gms.get(imt_key)
phi_cond = phi_gms.get(imt_key)

sigma = np.sqrt((f1 * sigma_cond) ** 2 + sigma_pgv ** 2)
tau = np.sqrt((f1 * tau_cond) ** 2 + tau_pgv ** 2) if (tau_cond is not None and np.size(tau_cond) > 0) else tau_pgv
phi = np.sqrt((f1 * phi_cond) ** 2 + phi_pgv ** 2) if (phi_cond is not None and np.size(phi_cond) > 0) else phi_pgv
return sigma, tau, phi


class AbrahamsonBhasin2020(GMPE):
"""Implementation of a conditional GMPE of Abrahamson & Bhasin (2020)
for Peak Ground Velocity (PGV) applicable to active shallow crustal
earthquakes. This requires characterisation of the SA at reference
period (which is magnitude-dependent), in addition to magnitude and vs30,
to define PGV and propagate the associated uncertainty. It also includes
single-period SA variants (e.g., PGA and SA(1.0)), designed for use with
seismic design maps that typically provide SA values at only a few spectral
periods.

Abrahamson N, Bhasin S (2020) "Conditional Ground-Motion Model for Peak Ground
Velocity for Active Crustal Regions.", PEER Report 2020/05, Pacific Earthquake
Engineering Research Center Headquarters, University of California at Berkeley.
(October 2010)

"""
DEFINED_FOR_TECTONIC_REGION_TYPE = const.TRT.ACTIVE_SHALLOW_CRUST
DEFINED_FOR_INTENSITY_MEASURE_TYPES = {PGV}
DEFINED_FOR_INTENSITY_MEASURE_COMPONENT = const.IMC.RotD50
DEFINED_FOR_STANDARD_DEVIATION_TYPES = {
const.StdDev.TOTAL, const.StdDev.INTER_EVENT, const.StdDev.INTRA_EVENT
}
REQUIRES_SITES_PARAMETERS = {"vs30"}
REQUIRES_RUPTURE_PARAMETERS = {"mag"}
REQUIRES_DISTANCES = {"rrup"}

non_verified = True

def __init__(self, kind: str = "general", **gmpe_dict):

if kind not in AB20_COEFFS:
raise ValueError(f"Unknown AB20 kind '{kind}'. Choose from {list(AB20_COEFFS)}")
[(gmpe_name, kw)] = gmpe_dict.pop("gmpe").items()
self.gmpe = registry[gmpe_name](**kw)
self.c = AB20_COEFFS[kind]
self.kind = kind
self.last_tref = None

def compute(self, ctx: np.recarray, imts: list,
mean: np.ndarray, sig: np.ndarray, tau: np.ndarray, phi: np.ndarray):
if self.kind == "general":
tref = _get_tref(ctx)
self.last_tref = tref
cond_imt = SA(tref)
elif self.kind == "pga-based":
cond_imt = PGA()
else:
cond_imt = SA(1.0)

key = str(cond_imt)

mean_gms, sigma_gms, tau_gms, phi_gms = get_mean_stds(
self.gmpe, ctx, {cond_imt}, return_dicts=True
)

lnpgv = get_mean_conditional_pgv(self.c, ctx, mean_gms, key)
sigma_pgv, tau_pgv, phi_pgv = get_standard_deviations(
self.c, ctx, sigma_gms=sigma_gms, tau_gms=tau_gms, phi_gms=phi_gms, imt_key=key
)

mean[0] = lnpgv
sig[0] += sigma_pgv
tau[0] += tau_pgv
phi[0] += phi_pgv


class AbrahamsonBhasin2020PGA(AbrahamsonBhasin2020):
DEFINED_FOR_TECTONIC_REGION_TYPE = const.TRT.ACTIVE_SHALLOW_CRUST
DEFINED_FOR_INTENSITY_MEASURE_TYPES = {PGV}
def __init__(self, **gmpe_dict):
super().__init__(kind="pga-based", **gmpe_dict)


class AbrahamsonBhasin2020SA1(AbrahamsonBhasin2020):
DEFINED_FOR_TECTONIC_REGION_TYPE = const.TRT.ACTIVE_SHALLOW_CRUST
DEFINED_FOR_INTENSITY_MEASURE_TYPES = {PGV}
def __init__(self, **gmpe_dict):
super().__init__(kind="sa1_based", **gmpe_dict)
Loading