ground_motion_lib/mf2013.rs
1//! Implementation of Morikawa & Fujiwara (2013) Ground Motion Prediction Equations (GMPE).
2//!
3//! This module defines the parameters and calculation logic for predicting
4//! ground motion values (PGA, PGV, PSA) based on earthquake and site characteristics.
5
6use crate::auxilary::{DL, G_GLOBAL};
7use crate::gmm::{Earthquake, GmpePoint, GmpePointKind, GroundMotionModeling, Vs30Point};
8use geo::{Distance, Haversine, Point};
9
10/// Morikawa & Fujiwara (2013) Ground Motion Prediction Equation parameters.
11#[derive(Debug)]
12pub struct MF2013 {
13 /// Magnitude upper limit (Mw0)
14 pub mw0: f64,
15 /// Coefficient for magnitude scaling
16 pub a: f64,
17 /// Coefficient for distance scaling
18 pub b: f64,
19 /// Constant term
20 pub c: f64,
21 /// Distance damping parameter
22 pub d: f64,
23 /// Exponent scaling factor for distance damping
24 pub e: f64,
25 /// Standard deviation of the log ground motion (not currently used)
26 pub sigma: f64,
27 /// Coefficient for deep sedimentary layer correction
28 pub pd: f64,
29 /// Minimum depth for deep sedimentary layer correction
30 pub dl_min: f64,
31 /// Reference depth for deep layer correction
32 pub d0: f64,
33 /// Coefficient for Vs30 amplification term
34 pub ps: f64,
35 /// Maximum Vs30 considered for amplification (Vs_max)
36 pub vs_max: f64,
37 /// Reference Vs30 value (V0)
38 pub v0: f64,
39 /// Coefficient for anomalous seismic intensity distribution (ASID)
40 pub gamma: f64,
41 /// Whether ASID correction is enabled
42 pub asid: bool,
43 /// Type of motion (PGA, PGV, PSA etc.)
44 pub motion_kind: GmpePointKind,
45}
46
47impl MF2013 {
48 /// Calculate predicted ground motion value (in physical units) for a site and earthquake.
49 ///
50 /// Note: Currently assumes a point source (no finite fault modeling).
51 ///
52 /// # Arguments
53 ///
54 /// * `epicentral_distance` - Horizontal distance from the site to the earthquake epicenter (km).
55 /// * `eq_mag` - Earthquake moment magnitude (Mw).
56 /// * `eq_depth` - Hypocentral depth (km).
57 /// * `vs_30` - Average shear-wave velocity in the top 30 meters at the site (m/s).
58 /// * `dl` - Depth to the 1400 m/s shear-wave velocity layer (m).
59 /// * `xvf` - Binary flag for volcanic front effect (1.0 if oceanward of front, 0.0 otherwise).
60 ///
61 /// # Returns
62 ///
63 /// Predicted ground motion value in cm/s² (PGA, PSA) or cm/s (PGV).
64 fn get_gmpe_by_distnace(
65 &self,
66 epicentral_distance: f64,
67 eq_mag: f64,
68 eq_depth: f64,
69 vs_30: f64,
70 dl: f64,
71 xvf: f64,
72 ) -> f64 {
73 // Rupture distance assuming point source
74 let r_rup = (epicentral_distance.powi(2) + eq_depth.powi(2)).sqrt();
75
76 let magnitude = eq_mag.min(self.mw0);
77 let a_m_w = self.a * magnitude;
78
79 // Deep sedimentary layer correction
80 let g_d = self.pd * (dl.max(self.dl_min) / self.d0).log10();
81
82 // Main GMPE equation (log10 of predicted motion)
83 // logA where A in cm/s^2 (pga,psa) or cm/s (pgv)
84 let log_a = (a_m_w + self.b * r_rup + self.c)
85 - (r_rup + self.d * 10.0_f64.powf(self.e * magnitude)).log10();
86
87 // Amplification by Deep Sedimentary Layers
88 // Apply deep layer correction
89 let log_agd = log_a + g_d;
90
91 // Vs30 site amplification
92 let gs = self.ps * (vs_30.min(self.vs_max) / self.v0).log10();
93 let log_ags = log_agd + gs;
94
95 // Optional anomalous seismic intensity distribution correction
96 if self.asid {
97 let ai = self.gamma + xvf * (eq_depth - 30.);
98 10.0_f64.powf(log_ags + ai)
99 } else {
100 10.0_f64.powf(log_ags)
101 }
102 }
103}
104
105impl GroundMotionModeling for MF2013 {
106 /// Compute ground motion prediction at a given site point for a specified earthquake event.
107 ///
108 /// # Arguments
109 ///
110 /// * `point` - The site location and properties (longitude, latitude, Vs30, depth to 1400 m/s
111 /// layer, etc.).
112 /// * `eq` - The earthquake event (magnitude, depth, hypocenter location).
113 ///
114 /// # Returns
115 ///
116 /// A `GmpePoint` containing the predicted ground motion value and associated metadata.
117 fn calc_from_point(&self, point: &Vs30Point, eq: &Earthquake) -> GmpePoint {
118 let epicentral_distance = Haversine
119 .distance(Point::new(eq.lon, eq.lat), Point::new(point.lon, point.lat))
120 / 1000.;
121 let vs_30 = point.vs30;
122 let dl = match point.dl {
123 None => DL as f64,
124 Some(dl) => dl,
125 };
126 let xvf = match point.xvf {
127 None => 0.,
128 Some(_) => 1.,
129 };
130 let mut ground_motion =
131 self.get_gmpe_by_distnace(epicentral_distance, eq.magnitude, eq.depth, vs_30, dl, xvf);
132 // convert cm/c^2 to %g
133 if matches!(self.motion_kind, GmpePointKind::Pga | GmpePointKind::Psa) {
134 ground_motion = ((ground_motion / 100.) / G_GLOBAL) * 100.;
135 };
136 GmpePoint {
137 lon: point.lon,
138 lat: point.lat,
139 value: ground_motion,
140 kind: self.motion_kind,
141 }
142 }
143}