# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2025-2026 Djura | Risk - Data - Engineering S.r.l.
import numpy as np
from typing import List
[docs]
class PFAProfile:
# TODO, add more approaches for pfa estimation similar to Muho
# check Mo's paper for more references
omega = np.array([])
# Empirical parameters for quantification of acceleration amplification
# factor for infilled RC frames: Muho E V, Pian C, Qian J, Shadabfar M,
# Beskos DE. Deformation-dependent peak floor acceleration for the
# performance-based design of nonstructural elements attached to R/C
# structures. Earthquake Spectra 2021; 37(2): 1035–1055.
# DOI: 10.1177/8755293020988015.
pfa_coefs = {
"muho": {
"rc-infill": {
"2-4": [
[0.645, -0.178, -0.148, -0.090, 0.136],
[0.308, -0.460, 0.055, -0.168, 0.259],
],
"5-20": [
[1.159, -0.027, -0.170, -0.048, 0.076],
[0.708, -0.145, -0.206, -0.103, 0.087],
[0.161, -0.463, -0.336, -0.291, 0.145],
[0.259, -0.438, -0.256, -0.270, 0.196],
]
},
"rc-mrf": {
"2-4": [
[0.230, -0.355, 0.213],
[0.046, -0.770, 0.297],
],
"5-20": [
[0.923, -0.029, -0.100],
[0.636, -0.151, -0.154],
[0.254, -0.340, -0.026],
[0.135, -0.537, -0.025],
]
}
}
}
def __init__(
self, method: str, bldg_type: str, psd: List,
period: float, heights: List):
self.method = method.lower()
self.bldg_type = bldg_type.lower()
self.psd = np.asarray(psd)
self.period = period
self.heights = heights
self.nst = len(heights)
if self.method == "muho":
self._muho()
else:
raise ValueError(f"Method: {self.method} not supported...")
def _muho(self):
# TODO add I-MRF and WFDS support for Muho's approach
if "infil" in self.bldg_type:
bldg_type = "rc-infill"
method = self._muho_infill
elif "frame" in self.bldg_type or "mrf" in self.bldg_type:
bldg_type = "rc-mrf"
method = self._muho_mrf
else:
raise ValueError(f"Building typology {bldg_type} not supported...")
if self.nst <= 4:
coefs = self.pfa_coefs["muho"][bldg_type]["2-4"]
elif self.nst <= 20:
coefs = self.pfa_coefs["muho"][bldg_type]["5-20"]
else:
raise ValueError(
f"Number of storeys {self.nst} for method {self.method} are "
"not supported, allowed number of storeys from 2 to 20")
self.omega = method(coefs)
def _muho_infill(self, coefs):
raise ValueError("Infill for Muho not supported yet...")
def _muho_mrf(self, coefs):
coefs = np.asarray(coefs)
omegas = np.zeros((self.psd.shape[0] + 1, self.psd.shape[1]))
omegas[0] = 1
h = np.cumsum(self.heights)
h = np.insert(h, 0, 0)
# Get the maximum PSD along the height of the structure
mpsd = np.max(self.psd, axis=0)
mpsd[mpsd < 0.005] = 0.005
if self.nst == 2:
omegas[1:] = coefs[:, 0].reshape(2, 1) * \
mpsd ** coefs[:, 1].reshape(2, 1) * \
self.period ** coefs[:, 2].reshape(2, 1)
return omegas
elif 3 <= self.nst <= 4:
omegas[1] = self._muho_mrf_omega(mpsd, *coefs[0])
omegas[-1] = self._muho_mrf_omega(mpsd, *coefs[1])
# Interpolate for the remaining omegas
omegas[3] = omegas[-1] - (omegas[-1] - omegas[1]) * ((
h[-1] - h[3]) / (h[-1] - h[1]))
omegas[2] = omegas[-1] - (omegas[-1] - omegas[1]) * ((
h[-1] - h[2]) / (h[-1] - h[1]))
else:
omegas[1] = self._muho_mrf_omega(mpsd, *coefs[0])
omegas[2] = self._muho_mrf_omega(mpsd, *coefs[1])
omegas[-2] = self._muho_mrf_omega(mpsd, *coefs[2])
omegas[-1] = self._muho_mrf_omega(mpsd, *coefs[3])
# Interpolate for the remaining omegas
omegas[0] = h[0] / h[1] * (omegas[1] - omegas[0]) + omegas[0]
for i, _ in enumerate(omegas[3:-2], start=3):
omegas[i] = (h[i] - h[2]) * (omegas[-2] - omegas[2]) / (
h[-2] - h[2]) + omegas[2]
return omegas
def _muho_mrf_omega(self, psd, a1, a2, a3):
return a1 * psd ** a2 * self.period ** a3