Source code for structdyn.ground_motions.ground_motion

from pathlib import Path
import numpy as np
import pandas as pd
import re
from importlib import resources


[docs] class GroundMotion: """Represents a ground motion acceleration time history. This class provides a container for ground motion data, typically acceleration time histories. It includes methods for reading data from common file formats (like AT2), scaling the motion, and accessing its properties. Attributes ---------- acc_g : numpy.ndarray The acceleration time history in units of g (acceleration due to gravity). dt : float The time step of the acceleration data. time : numpy.ndarray The time vector corresponding to the acceleration data. name : str, optional A name for the ground motion event. component : str, optional The component of the ground motion (e.g., 'h1', 'h2', 'v'). """ def __init__(self, acc_g, dt, name=None, component=None): """Initializes the GroundMotion object. Parameters ---------- acc_g : array-like Acceleration time history in units of g. dt : float Time step of the acceleration data. name : str, optional Name of the ground motion event, by default None. component : str, optional Component of the ground motion, by default None. """ self.acc_g = np.asarray(acc_g) self.dt = float(dt) self.time = np.arange(len(acc_g)) * dt self.name = name self.component = component # ---------- Constructors ----------
[docs] @classmethod def from_at2(cls, file_path): """Creates a GroundMotion object from a PEER NGA (AT2) file. Parameters ---------- file_path : str or pathlib.Path The path to the .AT2 file. Returns ------- GroundMotion A new GroundMotion instance with data from the file. """ file_path = Path(file_path) acc, dt = cls._read_at2(file_path) return cls(acc, dt, name=file_path.stem)
[docs] @classmethod def from_event(cls, event_name, component="hor1", base_dir=None): """Loads a ground motion from the built-in event database. Parameters ---------- event_name : str The name of the earthquake event. Currently available are (e.g., 'imperialValley_elCentro_1940', 'lomaPrieta_corralitos_1989', 'northridge_sylmar_1994', 'sanFernando_pacoidaDam_1971' ). component : str The specific component to load (e.g., 'hor1', 'hor2', 'up'). base_dir : str or pathlib.Path, optional The base directory of the ground motion data. If None, it uses the package's default data directory. Returns ------- GroundMotion A new GroundMotion instance for the specified event and component. Raises ------- FileNotFoundError If the specified event or component is not found. """ if base_dir is None: with resources.as_file( resources.files("structdyn.ground_motions") / "data" ) as data_dir: base_dir = Path(data_dir) event_dir = base_dir / event_name if not event_dir.exists(): raise FileNotFoundError(f"Event '{event_name}' not found") files = list(event_dir.glob("*.AT2")) if not files: raise FileNotFoundError("No AT2 files found") selected = cls._select_component(files, component) acc, dt = cls._read_at2(selected) return cls(acc, dt, name=event_name, component=component)
[docs] @classmethod def from_arrays(cls, acc_g, dt, name="user_motion"): """Creates a GroundMotion object directly from arrays. Parameters ---------- acc_g : array-like Acceleration time history in units of g. dt : float Time step of the acceleration data. name : str, optional A name for the motion, by default "user_motion". Returns ------- GroundMotion A new GroundMotion instance. """ return cls(acc_g, dt, name=name)
# ---------- Utilities ---------- @staticmethod def _read_at2(file_path): acc = [] dt = None with open(file_path, "r") as f: lines = f.readlines() for i, line in enumerate(lines): if "NPTS" in line and "DT" in line: dt = float(re.search(r"DT=\s*([0-9.]+)", line).group(1)) data_start = i + 1 break for line in lines[data_start:]: acc.extend(float(x) for x in line.split()) return np.array(acc), dt @staticmethod def _select_component(files, component): component = component.lower() for f in files: if component in f.stem.lower(): return f raise ValueError(f"Component '{component}' not found") # ---------- Operations ----------
[docs] def scale(self, factor): self.acc_g *= factor return self
[docs] def scale_to_pga(self, target_pga_g): current = np.max(np.abs(self.acc_g)) self.acc_g *= target_pga_g / current return self
[docs] def to_dataframe(self): return pd.DataFrame({"time": self.time, "acc_g": self.acc_g})