Source code for djura.vulnerability_modeller.pfa_profile

# 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