Source code for openquake.pfd.secondary_surf_rup.ferrario2021
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2024-2026 Yen-Shin Chen, OGS
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
"""
Module :mod:`openquake.pfd.secondary_surf_rup.ferrario2021` implements the
model of Ferrario and Livio (2021) in :class:`FerrarioLivio2021SecondarySR`.
Supported Fault Styles: Normal only
References
----------
Ferrario, M. F., & Livio, F. (2021). Conditional probability of distributed
surface rupturing during normal-faulting earthquakes. Solid Earth, 12(5),
1197-1209. https://doi.org/10.5194/se-12-1197-2021
"""
import numpy as np
from openquake.pfd.params import check_choice
from openquake.pfd.secondary_surf_rup.base import BaseSecondarySurfRup
[docs]class FerrarioLivio2021SecondarySR(BaseSecondarySurfRup):
"""
Implementation of the Ferrario and Livio (2021) model for normal faults.
"""
# Coefficients for different versions and HW/FW positions
# From Table 2 in Ferrario and Livio (2021)
COEFFS = {
'regular': {
'HW': {'a': -2.254, 'b': -1.175, 'c': 1.0e-5},
'FW': {'a': -3.459, 'b': -1.903, 'c': 1.008e-5}
},
'conservative': {
'HW': {'a': -1.888, 'b': -0.8802, 'c': 1.009e-5},
'FW': {'a': -2.505, 'b': -1.181, 'c': 1.006e-5}
}
}
def __init__(self, version=None):
"""
:param version: optional variant pinned by the logic-tree branch
('regular' or 'conservative'); ``None`` defers to the
``get_prob`` call (legacy default: 'regular').
"""
self.version = check_choice(
type(self).__name__, "version", version,
frozenset(["regular", "conservative"]),
canon=lambda v: str(v).lower())
def get_prob(self, r, rx, version=None):
"""
Calculates probability of distributed surface rupture for normal faults.
This model estimates the conditional probability of distributed surface
rupturing as a function of distance from the principal fault trace,
calibrated for a 500 x 500 m pixel size.
:param r:
Distance to the principal fault trace in km (scalar or array).
:param rx:
Horizontal distance to surface projection of fault (scalar or array).
Positive values indicate hanging wall, negative values indicate footwall.
:param version:
Model version. Options are:
- 'regular': Standard model (default)
- 'conservative': Conservative estimate with higher probabilities
:return:
Probability of distributed surface rupture (0-1).
"""
# Fall back to constructor-pinned value, then legacy default
if version is None:
version = self.version if self.version is not None else "regular"
# Validate version
version = version.lower()
if version not in self.COEFFS:
raise ValueError(
f"Invalid version '{version}'. "
f"Accepted values are: {', '.join(self.COEFFS)}"
)
# Convert inputs to arrays
r = np.atleast_1d(np.asarray(r, dtype=float))
rx = np.atleast_1d(np.asarray(rx, dtype=float))
# Broadcast r to match rx shape if scalar
if r.size == 1 and rx.size > 1:
r = np.full_like(rx, r.item())
elif r.shape != rx.shape and r.size > 1:
raise ValueError("r must be a scalar or have the same shape as rx")
# Initialize coefficient arrays
a = np.zeros_like(rx, dtype=float)
b = np.zeros_like(rx, dtype=float)
c = np.zeros_like(rx, dtype=float)
# Masks for hanging wall (rx > 0) and footwall (rx <= 0)
hw_mask = rx > 0
fw_mask = ~hw_mask
# Assign coefficients based on version and position
coeffs_hw = self.COEFFS[version]['HW']
coeffs_fw = self.COEFFS[version]['FW']
a[hw_mask] = coeffs_hw['a']
b[hw_mask] = coeffs_hw['b']
c[hw_mask] = coeffs_hw['c']
a[fw_mask] = coeffs_fw['a']
b[fw_mask] = coeffs_fw['b']
c[fw_mask] = coeffs_fw['c']
# Compute probability using logistic regression
# Equation from Ferrario and Livio (2021)
fx = a + b * np.log(r + c)
prob = np.exp(fx) / (1.0 + np.exp(fx))
return prob.item() if prob.size == 1 else prob