source_modelling.ccldpy
1# Sourced from: https://github.com/tristanbuckreis/ccldpy 2# This package is not included as a dependency but packaged 3# directly because it cannot be installed via pip. 4# ccldpy v2.0.0 5# June 12, 2024 6# Tristan E. Buckreis (tristanbuckreis@ucla.edu) 7 8########################################################################################################################################### 9 10import numpy as np 11import pandas as pd 12 13########################################################################################################################################### 14 15 16TECTONIC_MAPPING = { 17 "Crustal": "crustal", 18 "Slab": "intraslab", 19 "Interface": "interface", 20 "Outer-rise": "intraslab", 21 "Undetermined": "crustal", 22} 23 24 25def WellsCoppersmith1994(magnitude, eqType): 26 """ 27 Magnitude-Scaling relationships defined by Wells & Coppersmith (1994) for all style-of-faultings. 28 29 Input Arguments: 30 - magnitude = moment magnitude (Mw) 31 - eqType = type of earthquake (tectonic regime) 32 "crustal" = shallow-crustal in active tectonic regimes 33 "intraslab" = intraslab-type in subduction regimes 34 "interface" = interface-type in subduction regimes 35 "stable" = shallow-crustal in stable-continental regimes 36 37 Output Arguments: 38 - A = rupture area (km^2) 39 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 40 - L = rupture along-strike length (km) 41 - W = rupture down-dip width (km) 42 """ 43 if eqType in ["intraslab", "interface", "stable"]: 44 raise ValueError( 45 "eqType: WellsCoppersmith1994 cannot be used for subduction-type or stable-continental events ('intraslab', 'interface', or 'stable')" 46 ) 47 return (np.nan, np.nan, np.nan, np.nan) 48 49 elif eqType == "crustal": 50 # Rupture Area: 51 a1 = -3.49 52 b1 = 0.91 53 s1 = 0.24 54 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 55 # Rupture Length: 56 a2 = -2.44 57 b2 = 0.59 58 s2 = 0.16 59 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 60 # Aspect Ratio: 61 AR = L**2 / A 62 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 63 if AR < 1.0: 64 # AR = 1.0 65 s_cy08 = 0.16 66 AR = np.random.normal(1, s_cy08) 67 L = np.sqrt(A * AR) 68 W = np.sqrt(A / AR) 69 else: 70 W = L / AR 71 return (A, AR, L, W) 72 73 74def Leonard2014(magnitude, mechanism, eqType): 75 """ 76 Magnitude-Scaling relationships defined by Leonard (2014) for different tectonic regimes and style-of-faultings. 77 78 Input Arguments: 79 - magnitude = moment magnitude (Mw) 80 - mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) 81 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 82 "NM" = normal: -150 < rake < -30 83 "RV" = reverse: 30 < rake < 150 84 - eqType = type of earthquake (tectonic regime) 85 "crustal" = shallow-crustal in active tectonic regimes 86 "intraslab" = intraslab-type in subduction regimes 87 "interface" = interface-type in subduction regimes 88 "stable" = shallow-crustal in stable-continental regimes 89 90 Output Arguments: 91 - A = rupture area (km^2) 92 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 93 - L = rupture along-strike length (km) 94 - W = rupture down-dip width (km) 95 """ 96 if eqType in ["intraslab", "interface"]: 97 raise ValueError( 98 "eqType: Leonard2014 cannot be used for subduction-type events ('intraslab' or 'interface')" 99 ) 100 return (np.nan, np.nan, np.nan, np.nan) 101 102 elif eqType == "crustal": 103 if mechanism == "SS": 104 # Rupture Area: 105 a1 = 3.99 106 b1 = 1.00 107 s1 = 0.13 108 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 109 # Rupture Length: 110 a2 = 4.170 111 b2 = 1.667 112 s2 = 0.19 113 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 114 if L > 45.0: 115 a3 = 5.27 116 b3 = 1.000 117 L = 10 ** ((magnitude - a3 - np.random.normal(0, s2)) / b3) 118 elif mechanism in ["NM", "RV"]: 119 # Rupture Area: 120 a1 = 4.00 121 b1 = 1.00 122 s1 = 0.15 123 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 124 # Rupture Length: 125 a2 = 4.000 126 b2 = 2.000 127 s2 = 0.23 128 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 129 if L > 5.4: 130 a3 = 4.240 131 b3 = 1.667 132 L = 10 ** ((magnitude - a3 - np.random.normal(0, s2)) / b3) 133 # Aspect Ratio: 134 AR = L**2 / A 135 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 136 if AR < 1.0: 137 # AR = 1.0 138 s_cy08 = 0.16 139 AR = np.random.normal(1, s_cy08) 140 L = np.sqrt(A * AR) 141 W = np.sqrt(A / AR) 142 else: 143 W = L / AR 144 return (A, AR, L, W) 145 146 elif eqType == "stable": 147 if mechanism == "SS": 148 # Rupture Area: 149 a1 = 4.18 150 b1 = 1.00 151 s1 = 0.09 152 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 153 # Rupture Length: 154 a2 = 4.250 155 b2 = 1.667 156 s2 = 0.18 157 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 158 if L > 60.0: 159 a3 = 5.44 160 b3 = 1.000 161 L = 10 ** ((magnitude - a3 - np.random.normal(0, s2)) / b3) 162 elif mechanism in ["NM", "RV"]: 163 # Rupture Area: 164 a1 = 4.19 165 b1 = 1.00 166 s1 = 0.10 167 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 168 # Rupture Length: 169 a2 = 4.320 170 b2 = 1.667 171 s2 = 0.19 172 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 173 # Aspect Ratio: 174 AR = L**2 / A 175 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 176 if AR < 1.0: 177 # AR = 1.0 178 s_cy08 = 0.16 179 AR = np.random.normal(1, s_cy08) 180 L = np.sqrt(A * AR) 181 W = np.sqrt(A / AR) 182 else: 183 W = L / AR 184 return (A, AR, L, W) 185 186 187def ThingbaijamEtAl2017(magnitude, mechanism, eqType): 188 """ 189 Magnitude-Scaling relationships defined by Thingbaijam et al. (2017) for different tectonic regimes and style-of-faultings. 190 191 Input Arguments: 192 - magnitude = moment magnitude (Mw) 193 - mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) 194 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 195 "NM" = normal: -150 < rake < -30 196 "RV" = reverse: 30 < rake < 150 197 - eqType = type of earthquake (tectonic regime) 198 "crustal" = shallow-crustal in active tectonic regimes 199 "intraslab" = intraslab-type in subduction regimes 200 "interface" = interface-type in subduction regimes 201 "stable" = shallow-crustal in stable-continental regimes 202 203 Output Arguments: 204 - A = rupture area (km^2) 205 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 206 - L = rupture along-strike length (km) 207 - W = rupture down-dip width (km) 208 """ 209 if eqType in ["intraslab", "stable"]: 210 raise ValueError( 211 "eqType: ThingbaijamEtAl2017 cannot be used for instra-slab or stable-continental events ('intraslab' or 'stable')" 212 ) 213 return (np.nan, np.nan, np.nan, np.nan) 214 215 elif eqType == "crustal": 216 if mechanism == "SS": 217 # Rupture Area: 218 a1 = -3.486 219 b1 = 0.942 220 s1 = 0.184 221 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 222 # Rupture Length: 223 a2 = -2.943 224 b2 = 0.681 225 s2 = 0.151 226 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 227 elif mechanism == "NM": 228 # Rupture Area: 229 a1 = -2.551 230 b1 = 0.808 231 s1 = 0.181 232 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 233 # Rupture Length: 234 a2 = -1.722 235 b2 = 0.485 236 s2 = 0.128 237 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 238 elif mechanism == "RV": 239 # Rupture Area: 240 a1 = -4.362 241 b1 = 1.049 242 s1 = 0.121 243 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 244 # Rupture Length: 245 a2 = -2.693 246 b2 = 0.614 247 s2 = 0.083 248 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 249 # Aspect Ratio: 250 AR = L**2 / A 251 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 252 if AR < 1.0: 253 # AR = 1.0 254 s_cy08 = 0.16 255 AR = np.random.normal(1, s_cy08) 256 L = np.sqrt(A * AR) 257 W = np.sqrt(A / AR) 258 else: 259 W = L / AR 260 return (A, AR, L, W) 261 262 elif eqType == "interface": 263 # Rupture Area: 264 a1 = -3.292 265 b1 = 0.949 266 s1 = 0.150 267 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 268 # Rupture Length: 269 a2 = -2.412 270 b2 = 0.583 271 s2 = 0.107 272 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 273 # Aspect Ratio: 274 AR = L**2 / A 275 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 276 if AR < 1.0: 277 # AR = 1.0 278 s_cy08 = 0.16 279 AR = np.random.normal(1, s_cy08) 280 L = np.sqrt(A * AR) 281 W = np.sqrt(A / AR) 282 else: 283 W = L / AR 284 return (A, AR, L, W) 285 286 287def ChiouYoungs2008(magnitude, mechanism, eqType, model): 288 """ 289 Magnitude-Scaling aspect ratio relationship defined by Chiou & Youngs (2008) for different style-of-faultings 290 in combination with other published magnitude-scaling area relationships. 291 292 Input Arguments: 293 - magnitude = moment magnitude (Mw) 294 - mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) 295 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 296 "NM" = normal: -150 < rake < -30 297 "RV" = reverse: 30 < rake < 150 298 - eqType = type of earthquake (tectonic regime) 299 "crustal" = shallow-crustal in active tectonic regimes 300 "intraslab" = intraslab-type in subduction regimes 301 "interface" = interface-type in subduction regimes 302 "stable" = shallow-crustal in stable-continental regimes 303 - model = magntiude-scaling area relationship to use with Chiou & Youngs (2008) aspect ratio relation 304 "WellsCoppersmith1994" = Wells & Coppersmith (1994) 305 "Leonard2014" = Leonard (2014) 306 "ThingbaijamEtAl2017" = Thingbaijam et al. (2017) 307 308 Output Arguments: 309 - A = rupture area (km^2) 310 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 311 - L = rupture along-strike length (km) 312 - W = rupture down-dip width (km) 313 """ 314 if eqType in ["intraslab", "interface", "stable"]: 315 raise ValueError( 316 "eqType: ChiouYoungs2008 cannot be used for subduction-type or stable-continental events ('intraslab', 'interface', or 'stable')" 317 ) 318 return (np.nan, np.nan, np.nan, np.nan) 319 320 elif eqType == "crustal": 321 if model == "WellsCoppersmith1994": 322 # Rupture Area: 323 a1 = -3.49 324 b1 = 0.91 325 s1 = 0.24 326 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 327 elif model == "Leonard2014": 328 if mechanism == "SS": 329 # Rupture Area: 330 a1 = 3.99 331 b1 = 1.00 332 s1 = 0.13 333 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 334 elif mechanism in ["NM", "RV"]: 335 # Rupture Area: 336 a1 = 4.00 337 b1 = 1.00 338 s1 = 0.15 339 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 340 elif model == "ThingbaijamEtAl2017": 341 if mechanism == "SS": 342 # Rupture Area: 343 a1 = -3.486 344 b1 = 0.942 345 s1 = 0.184 346 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 347 elif mechanism == "NM": 348 # Rupture Area: 349 a1 = -2.551 350 b1 = 0.808 351 s1 = 0.181 352 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 353 elif mechanism == "RV": 354 # Rupture Area: 355 a1 = -4.362 356 b1 = 1.049 357 s1 = 0.121 358 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 359 # Aspect Ratio: 360 s = 0.16 361 if magnitude < 4.0: 362 # AR = 1.0 363 AR = np.random.normal(1, s) 364 else: 365 FNM = 0.0 366 FRV = 0.0 367 if mechanism == "NM": 368 FNM = 1.0 369 elif mechanism == "RV": 370 FRV = 1.0 371 ass = 0.01752 372 anm = -0.00472 373 arv = -0.01099 374 b = 3.097 375 AR = 10 ** ( 376 (ass + anm * FNM + arv * FRV) * (magnitude - 4.0) ** b 377 + np.random.normal(0, s) 378 ) 379 # Compute Length and Width: 380 L = np.sqrt(A * AR) 381 W = np.sqrt(A / AR) 382 return (A, AR, L, W) 383 384 385def ContrerasEtAl2022(magnitude, eqType): 386 """ 387 Magnitude-Scaling aspect ratio relationship defined by Contreras et al. (222) for subduction-type earthquakes. 388 389 Input Arguments: 390 - magnitude = moment magnitude (Mw) 391 - eqType = type of earthquake (tectonic regime) 392 "crustal" = shallow-crustal in active tectonic regimes 393 "intraslab" = intraslab-type in subduction regimes 394 "interface" = interface-type in subduction regimes 395 "stable" = shallow-crustal in stable-continental regimes 396 397 Output Arguments: 398 - A = rupture area (km^2) 399 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 400 - L = rupture along-strike length (km) 401 - W = rupture down-dip width (km) 402 """ 403 if eqType in ["crustal", "stable"]: 404 raise ValueError( 405 "eqType: ContrerasEtAl2022 cannot be used for non-subduction-type events ('crustal' or 'stable')" 406 ) 407 return (np.nan, np.nan, np.nan, np.nan) 408 409 elif eqType == "interface": 410 # Rupture Area: 411 a1 = -3.8290 412 b1 = 1.0 413 s1 = 0.270 414 A = 10.0 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 415 # Aspect Ratio: 416 if magnitude > 7.25: 417 AR = 10.0 ** ( 418 0.2759 * (magnitude - 7.25) + np.random.normal(0, 0.192) 419 ) # std = 0.441 in ln unit 420 else: 421 AR = 10.00 ** (0.0 + np.random.normal(0, 0.0717)) # std = 0.165 in ln unit 422 # Compute Length and Width: 423 L = np.sqrt(A * AR) 424 W = np.sqrt(A / AR) 425 return (A, AR, L, W) 426 427 elif eqType == "intraslab": 428 # Rupture Area: 429 a1 = -3.251 430 b1 = 0.890 431 s1 = 0.184 432 A = 10.0 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 433 # Aspect Ratio: 434 if magnitude > 6.5: 435 AR = 10.0 ** ( 436 0.0938 * (magnitude - 6.5) + np.random.normal(0, 0.164) 437 ) # std = 0.378in ln unit 438 else: 439 AR = 10.00 ** (0.0 + np.random.normal(0, 0.104)) # std = 0.239 in ln unit 440 # Compute Length and Width: 441 L = np.sqrt(A * AR) 442 W = np.sqrt(A / AR) 443 return (A, AR, L, W) 444 445 446def get_mechanism_based_on_rake(rake): 447 """ 448 Infer style-of-faulting (mechanism) from rake angle (e.g., Ancheta et al. 2013) 449 450 Input Arguments: 451 rake = rake angle (degrees) 452 453 Output Arguments: 454 mechanism = indicator string for style-of-faulting (mechanism) 455 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 456 "NM" = normal: -150 < rake < -30 457 "RV" = reverse: 30 < rake < 150 458 """ 459 if (-180 <= rake < -150) | (-30 <= rake < 30) | (150 <= rake <= 180): 460 return "SS" 461 elif (-120 <= rake < -60) | (-150 <= rake < -120) | (-60 <= rake < -30): 462 return "NM" 463 elif (60 <= rake < 120) | (30 <= rake < 60) | (120 <= rake < 150): 464 return "RV" 465 466 467def discrete(n, x, p, tp): 468 for i in range(10): 469 if (tp >= p[i]) and (tp <= p[i + 1]): 470 tx = x[i] + (x[i + 1] - x[i]) * (tp - p[i]) / (p[i + 1] - p[i]) 471 return tx 472 return 473 474 475def get_hyp_down_dip_position(eqtype, region): 476 """ 477 Simulate hypocenter's down-dip relative position on the rupture surface based on Chiou & Youngs (2008) 478 479 Input arguments: 480 - eqType = type of earthquake (tectonic regime) 481 "crustal" = shallow-crustal in active tectonic regimes 482 "intraslab" = intraslab-type in subduction regimes 483 "interface" = interface-type in subduction regimes 484 "stable" = shallow-crustal in stable-continental regimes 485 - region = geographic region where the earthquake occured 486 "japan" 487 "chile" 488 "other" 489 490 Output Arguments: 491 - fd = down-dip relative position of hypocenter on rupture surface [0, 1] 492 0 ~ along the top-depth of the rupture 493 1 ~ along the bottom-depth of the rupture 494 """ 495 nxf = 11 496 xdf = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.00]) 497 ssd = np.array([0, 0.025, 0.05, 0.1, 0.175, 0.275, 0.4, 0.55, 0.7, 0.85, 1.00]) 498 iad = np.array( 499 [0.00, 0.012, 0.051, 0.139, 0.294, 0.500, 0.706, 0.861, 0.949, 0.988, 1.00] 500 ) 501 ied0 = np.array( 502 [0.000, 0.024, 0.085, 0.206, 0.389, 0.599, 0.783, 0.906, 0.969, 0.993, 1.00] 503 ) 504 ied1 = np.array( 505 [0.0, 0.002, 0.012, 0.044, 0.121, 0.262, 0.460, 0.671, 0.843, 0.950, 1.00] 506 ) 507 ied2 = np.array( 508 [0.00, 0.013, 0.053, 0.143, 0.297, 0.500, 0.703, 0.857, 0.947, 0.987, 1.00] 509 ) 510 if eqtype in ["crustal", "stable"]: 511 fd = discrete(nxf, xdf, ssd, np.random.rand()) 512 elif eqtype == "intraslab": 513 fd = discrete(nxf, xdf, iad, np.random.rand()) 514 elif eqtype == "interface": 515 if region == "japan": 516 fd = discrete(nxf, xdf, ied0, np.random.rand()) 517 elif region == "chile": 518 fd = discrete(nxf, xdf, ied1, np.random.rand()) 519 elif region == "other": 520 fd = discrete(nxf, xdf, ied2, np.random.rand()) 521 try: 522 return fd 523 except: 524 return 0.0 525 526 527def get_hyp_along_strike_position(eqtype): 528 """ 529 Simulate hypocenter's along-strike relative position on the rupture surface based on Chiou & Youngs (2008) 530 531 Input arguments: 532 - eqType = type of earthquake (tectonic regime) 533 "crustal" = shallow-crustal in active tectonic regimes 534 "intraslab" = intraslab-type in subduction regimes 535 "interface" = interface-type in subduction regimes 536 "stable" = shallow-crustal in stable-continental regimes 537 538 Output Arguments: 539 - fl = along-strike relative position of hypocenter on rupture surface [0, 1] 540 0 ~ along the "left" edge of the rupture 541 1 ~ along the "right" edge of the rupture 542 """ 543 nxf = 11 544 xdf = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.00]) 545 hypx = np.array([0, 0.05, 0.125, 0.225, 0.35, 0.5, 0.65, 0.775, 0.875, 0.95, 1.00]) 546 hypxe = np.array( 547 [0.000, 0.007, 0.034, 0.112, 0.272, 0.500, 0.728, 0.888, 0.966, 0.993, 1.00] 548 ) 549 hypxa = np.array( 550 [0.00, 0.015, 0.057, 0.148, 0.301, 0.500, 0.699, 0.852, 0.943, 0.985, 1.00] 551 ) 552 if eqtype in ["crustal", "stable"]: 553 fl = discrete(nxf, xdf, hypx, np.random.rand()) 554 elif eqtype == "intraslab": 555 fl = discrete(nxf, xdf, hypxa, np.random.rand()) 556 elif eqtype == "interface": 557 fl = discrete(nxf, xdf, hypxe, np.random.rand()) 558 return fl 559 560 561def get_median_index(array): 562 index = np.argpartition(array, len(array) // 2)[len(array) // 2] 563 return index 564 565 566def LL2XY(zlon, zlat, lon, lat): 567 """ 568 Convert latitude/longitude to x/y coordinates on the basis of a spherical earth 569 (ignoring ellipsoidal effects) using the haversine formula. 570 571 Reference: http://www.movable-type.co.uk/scripts/latlong.html 572 573 Input Arguments: 574 - zlon = centered longitude (degrees): corresponding to x/y coordinate of (0,0) 575 - zlat = centered latitude (degrees): corresponding to x/y coordinate of (0,0) 576 - lon = numpy array of longitudes (degrees) 577 - lat = numpy array of latitudes (degrees) 578 579 Output Arguments: 580 -x = numpy array of x-coordinate 581 -y = numpy array of y-coordinates 582 583 """ 584 Rearth = 6371.0 585 dtr = np.arcsin(1.0) / 90 586 lam1 = (zlon + 360) * dtr if zlon < 0 else zlon * dtr 587 phi1 = zlat * dtr 588 589 # lam2 = (lon + 360)*dtr if lon < 0 else lon*dtr 590 if type(lon) == "float": 591 lam2 = (lon + 360) * dtr if lon < 0 else lon * dtr 592 else: 593 lam2 = np.where(lon < 0, (lon + 360) * dtr, lon * dtr) 594 595 phi2 = lat * dtr 596 dphi = phi2 - phi1 597 dlam = lam2 - lam1 598 a = np.sin(dphi / 2) ** 2 + np.cos(phi1) * np.cos(phi2) * np.sin(dlam / 2) ** 2 599 c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) 600 dist = Rearth * c 601 if dist == 0: 602 theta = 0 603 else: 604 theta = np.arctan2( 605 np.sin(dlam) * np.cos(phi2), 606 np.cos(phi1) * np.sin(phi2) - np.sin(phi1) * np.cos(phi2) * np.cos(dlam), 607 ) 608 x = dist * np.sin(theta) 609 y = dist * np.cos(theta) 610 611 return (x, y) 612 613 614def XY2LL(zlon, zlat, x, y): 615 """ 616 Convert x/y coordinates to latitude/longitude on the basis of a spherical earth 617 (ignoring ellipsoidal effects) using the haversine formula. 618 619 Reference: http://www.movable-type.co.uk/scripts/latlong.html 620 621 Input Arguments: 622 - zlon = centered longitude (degrees): corresponding to x/y coordinate of (0,0) 623 - zlat = centered latitude (degrees): corresponding to x/y coordinate of (0,0) 624 - x = numpy array of x-coordinate 625 - y = numpy array of y-coordinates 626 627 Output Arguments: 628 - lon = numpy array of longitudes (degrees) 629 - lat = numpy array of latitudes (degrees) 630 631 """ 632 Rearth = 6371.0 633 dtr = np.arcsin(1.0) / 90 634 lam1 = (zlon + 360) * dtr if zlon < 0 else zlon * dtr 635 phi1 = zlat * dtr 636 637 d = np.sqrt(x**2 + y**2) 638 delta = d / Rearth 639 theta = np.arctan2(x, y) 640 phi2 = np.arcsin( 641 np.sin(phi1) * np.cos(delta) + np.cos(phi1) * np.sin(delta) * np.cos(theta) 642 ) 643 lam2 = lam1 + np.arctan2( 644 np.sin(theta) * np.sin(delta) * np.cos(phi1), 645 np.cos(delta) - np.sin(phi1) * np.sin(phi2), 646 ) 647 lat = phi2 / dtr 648 lon = lam2 / dtr 649 if type(lon) == "float": 650 lon = lon - 360 if lon > 180 else lon 651 else: 652 lon = np.where(lon > 180, lon - 360, lon) 653 654 return (lon, lat) 655 656 657def pointTriangleDistance(TRI_xyz, P_xyz): 658 """ 659 Compute shortest 3D distance between a triangle and point (Eberly 1999) 660 661 Reference: https://www.geometrictools.com/Documentation/DistancePoint3Triangle3.pdf 662 663 Coded in Python by Meera Kota (UCLA) 664 665 Input Arguments: 666 - TRI_xyz = numpy arrays of 3D triangles in XY space [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3]] 667 - P_xyz = numpy array of 3D points in XY space [[x1,y1,z1],...,[xn,yn,zn]] where n = number of points 668 """ 669 E0 = TRI_xyz[:, 1] - TRI_xyz[:, 0] 670 E1 = TRI_xyz[:, 2] - TRI_xyz[:, 0] 671 a = np.sum(np.multiply(E0, E0), axis=1) 672 b = np.sum(np.multiply(E0, E1), axis=1) 673 c = np.sum(np.multiply(E1, E1), axis=1) 674 675 det = a * c - b * b 676 det[det == 0] = 1.0e-8 677 678 D = TRI_xyz[:, 0] - P_xyz 679 680 d = np.sum(np.multiply(E0, D), axis=2) 681 e = np.sum(np.multiply(E1, D), axis=2) 682 f = np.sum(np.multiply(D, D), axis=2) 683 684 s = b * e - c * d 685 t = b * d - a * e 686 687 sqrdistance = np.empty((len(P_xyz), len(TRI_xyz)), dtype=float) 688 689 # Region 4 690 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d < 0.0) & (-d >= a) 691 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 692 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d < 0.0) & (-d < a) 693 sqrdistance[cond] = (-d * d / a + f)[cond] 694 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d >= 0.0) & (e >= 0.0) 695 sqrdistance[cond] = (f)[cond] 696 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d >= 0.0) & (e < 0.0) & (-e >= c) 697 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 698 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d >= 0.0) & (e < 0.0) & (-e < c) 699 sqrdistance[cond] = (-e * e / c + f)[cond] 700 701 # Region 3 702 cond = (s + t <= det) & (s < 0.0) & (t >= 0.0) & (e >= 0.0) 703 sqrdistance[cond] = (f)[cond] 704 cond = (s + t <= det) & (s < 0.0) & (t >= 0.0) & (e < 0.0) & (-e >= c) 705 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 706 cond = (s + t <= det) & (s < 0.0) & (t >= 0.0) & (e < 0.0) & (-e < c) 707 sqrdistance[cond] = (-e * e / c + f)[cond] 708 709 # Region 5 710 cond = (s + t <= det) & (s >= 0.0) & (t < 0.0) & (d >= 0.0) 711 sqrdistance[cond] = (f)[cond] 712 cond = (s + t <= det) & (s >= 0.0) & (t < 0.0) & (d < 0.0) & (-d >= a) 713 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 714 cond = (s + t <= det) & (s >= 0.0) & (t < 0.0) & (d < 0.0) & (-d < a) 715 sqrdistance[cond] = (-d * d / a + f)[cond] 716 717 # Region 0 718 invDet = 1.0 / det 719 stemp = s * invDet 720 ttemp = t * invDet 721 cond = (s + t <= det) & (s >= 0) & (t >= 0) 722 sqrdistance[cond] = ( 723 stemp * (a * stemp + b * ttemp + 2.0 * d) 724 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 725 + f 726 )[cond] 727 728 # Region 2 729 tmp0 = b + d 730 tmp1 = c + e 731 numer = tmp1 - tmp0 732 denom = a - 2.0 * b + c 733 denom[denom == 0] = 1.0e-8 734 stemp = numer / denom 735 ttemp = 1.0 - stemp 736 cond = (s + t > det) & (s < 0.0) & (tmp1 > tmp0) & (numer >= denom) 737 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 738 cond = (s + t > det) & (s < 0.0) & (tmp1 > tmp0) & (numer < denom) 739 sqrdistance[cond] = ( 740 stemp * (a * stemp + b * ttemp + 2.0 * d) 741 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 742 + f 743 )[cond] 744 cond = (s + t > det) & (s < 0.0) & (tmp1 <= tmp0) & (tmp1 <= 0.0) 745 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 746 cond = (s + t > det) & (s < 0.0) & (tmp1 <= tmp0) & (tmp1 > 0.0) & (e >= 0.0) 747 sqrdistance[cond] = (f)[cond] 748 cond = (s + t > det) & (s < 0.0) & (tmp1 <= tmp0) & (tmp1 > 0.0) & (e < 0.0) 749 sqrdistance[cond] = (-e * e / c + f)[cond] 750 751 # Region 6 752 tmp0 = b + e 753 tmp1 = a + d 754 numer = tmp1 - tmp0 755 denom = a - 2.0 * b + c 756 denom[denom == 0] = 1.0e-8 757 ttemp = numer / denom 758 stemp = 1.0 - ttemp 759 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 > tmp0) & (numer >= denom) 760 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 761 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 > tmp0) & (numer < denom) 762 sqrdistance[cond] = ( 763 stemp * (a * stemp + b * ttemp + 2.0 * d) 764 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 765 + f 766 )[cond] 767 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 <= tmp0) & (tmp1 <= 0) 768 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 769 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 <= tmp0) & (tmp1 > 0) & (d >= 0) 770 sqrdistance[cond] = (f)[cond] 771 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 <= tmp0) & (tmp1 > 0) & (d < 0) 772 sqrdistance[cond] = (-d * d / a + f)[cond] 773 774 # Region 1 775 numer = c + e - b - d 776 denom = a - 2.0 * b + c 777 stemp = numer / denom 778 ttemp = 1.0 - stemp 779 cond = (s + t > det) & (s >= 0) & (t >= 0) & (numer <= 0) 780 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 781 cond = (s + t > det) & (s >= 0) & (t >= 0) & (numer > 0) & (numer >= denom) 782 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 783 cond = (s + t > det) & (s >= 0) & (t >= 0) & (numer > 0) & (numer < denom) 784 sqrdistance[cond] = ( 785 stemp * (a * stemp + b * ttemp + 2.0 * d) 786 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 787 + f 788 )[cond] 789 790 # account for numerical round-off error 791 sqrdistance[sqrdistance <= 0] = 0 792 return np.sqrt(sqrdistance).T 793 794 795def check_input_arguments( 796 eqType, method, nsims, strike, dip, rake, strike2, dip2, rake2 797): 798 # Check that required arguments are defined for each simulation method ------------------------------------------------- 799 if method in ["A", "D"]: 800 if None in np.array([strike, dip, rake]): 801 raise ValueError( 802 "'strike', 'dip', and 'rake' must be defined when using 'method' = '%s'" 803 % method 804 ) 805 if method == "B": 806 if None in np.array([strike2, dip2, rake2]): 807 raise ValueError( 808 "'strike2', 'dip2', and 'rake2' must be defined when using 'method' = 'B'" 809 ) 810 if method == "C": 811 if None in np.array([strike, dip, rake, strike2, dip2, rake2]): 812 raise ValueError( 813 "'strike', 'dip', 'rake', 'strike2', 'dip2', and 'rake2' must be defined when using 'method' = 'C'" 814 ) 815 816 # Check that number of simulations are compatible with sepcified eqType ------------------------------------------------ 817 if (eqType == "crustal") & (nsims[6] > 0): 818 raise ValueError( 819 "nsims[6] = %i: ContrerasEtAl2022 cannot be used for 'crustal' events" 820 % nsims[6] 821 ) 822 elif eqType == "stable": 823 if nsims[0] > 0: 824 raise ValueError( 825 "nsims[0] = %i: WellsCoppersmith1994 cannot be used for 'stable' events" 826 % nsims[0] 827 ) 828 elif nsims[2] > 0: 829 raise ValueError( 830 "nsims[2] = %i: ThingbaijamEtAl2017 cannot be used for 'stable' events" 831 % nsims[2] 832 ) 833 elif sum(nsims[3:6]) > 0: 834 raise ValueError( 835 "nsims[3:6] = [%i,%i,%i]: ChiouYoungs2008 cannot be used for 'stable' events" 836 % (nsims[3], nsims[4], nsims[5]) 837 ) 838 elif nsims[6] > 0: 839 raise ValueError( 840 "nsims[6] = %i: ContrerasEtAl2022 cannot be used for 'stable' events" 841 % nsims[6] 842 ) 843 elif eqType in ["interface", "intraslab"]: 844 if nsims[0] > 0: 845 raise ValueError( 846 "nsims[0] = %i: WellsCoppersmith1994 cannot be used for '%s' events" 847 % (nsims[0], eqType) 848 ) 849 elif nsims[1] > 0: 850 raise ValueError( 851 "nsims[1] = %i: Leonard2014 cannot be used for '%s' events" 852 % (nsims[1], eqType) 853 ) 854 elif sum(nsims[3:6]) > 0: 855 raise ValueError( 856 "nsims[3:6] = [%i,%i,%i]: ChiouYoungs2008 cannot be used for '%s' events" 857 % (nsims[3], nsims[4], nsims[5], eqType) 858 ) 859 860 # Check that there are only positive integers for the number of simulations -------------------------------------------- 861 if np.any(np.array(nsims), where=np.array(nsims) < 0): 862 raise ValueError( 863 "nsims = %s: can not specify a negative number of simulatiuons for any scaling relationship" 864 % str(list(nsims)) 865 ) 866 867 # Check that the total number of simulations is odd and greater than zero ---------------------------------------------- 868 if sum(nsims) % 2 == 0: 869 if eqType == "crustal": 870 if nsims[0] > 0: 871 nsims[0] = nsims[0] + 1 872 elif nsims[1] > 0: 873 nsims[1] = nsims[1] + 1 874 elif nsims[2] > 0: 875 nsims[2] = nsims[2] + 1 876 elif nsims[3] > 0: 877 nsims[3] = nsims[3] + 1 878 elif nsims[4] > 0: 879 nsims[4] = nsims[4] + 1 880 elif nsims[5] > 0: 881 nsims[5] = nsims[5] + 1 882 else: 883 raise ValueError( 884 "nsims = %s: must specify the number of simulations partitioned to each scaling relationship" 885 % str(list(nsims)) 886 ) 887 if eqType == "stable": 888 if nsims[1] > 0: 889 nsims[1] = nsims[1] + 1 890 else: 891 raise ValueError( 892 "nsims = %s: must specify the number of simulations partitioned to each scaling relationship" 893 % str(list(nsims)) 894 ) 895 if eqType in ["interface", "intraslab"]: 896 if nsims[6] > 0: 897 nsims[6] = nsims[6] + 1 898 elif nsims[2] > 0: 899 nsims[2] = nsims[2] + 1 900 else: 901 raise ValueError( 902 "nsims = %s: must specify the number of simulations partitioned to each scaling relationship" 903 % str(list(nsims)) 904 ) 905 906 907########################################################################################################################################### 908 909v_WellsCoppersmith1994 = np.vectorize(WellsCoppersmith1994) 910v_Leonard2014 = np.vectorize(Leonard2014) 911v_ThingbaijamEtAl2017 = np.vectorize(ThingbaijamEtAl2017) 912v_ChiouYoungs2008 = np.vectorize(ChiouYoungs2008) 913v_ContrerasEtAl2022 = np.vectorize(ContrerasEtAl2022) 914v_get_mechanism_based_on_rake = np.vectorize(get_mechanism_based_on_rake) 915v_get_hyp_down_dip_position = np.vectorize(get_hyp_down_dip_position) 916v_get_hyp_along_strike_position = np.vectorize(get_hyp_along_strike_position) 917v_get_median_index = np.vectorize(get_median_index, signature="(n)->()") 918v_LL2XY = np.vectorize(LL2XY, signature="(),(),(n),(n)->(n),(n)") 919v_XY2LL = np.vectorize(XY2LL, signature="(),(),(n),(n)->(n),(n)") 920 921########################################################################################################################################### 922 923 924def get_rupture_surface_simulations( 925 eqid, 926 eqType, 927 region, 928 elat, 929 elon, 930 hypd, 931 magnitude, 932 method, 933 nsims, 934 mechanism=None, 935 strike=None, 936 dip=None, 937 rake=None, 938 strike2=None, 939 dip2=None, 940 rake2=None, 941): 942 """ 943 Simulate earthquake rupture surface that minimizes the difference between the median distance of a pseudo-grid of sites 944 and a stochastic set of possible ruptures. Randomized fault attributes are dependent on the fault category. Fault scaling 945 models depend on earthquake type. Adapted from the CCLD routine originally coded in Frortran by Chiou and Youngs (2008). 946 947 Input Arguments: 948 - eqid = unique integer identifier for the event (to set the random seed) 949 - eqType = type of earthquake: 950 "crustal" = shallow-crustal in active tectonic regimes 951 "intraslab" = intraslab-type in subduction regimes 952 "interface" = interface-type in subduction regimes 953 "stable" = shallow-crustal in stable-continental regimes 954 - region = geographic region where the earthquake occured" 955 "japan" 956 "chile" 957 "other" 958 - elat = hypocenter latitude (degrees) 959 - elon = hypocenter longiude (degrees) 960 - hypd = hypocenter depth (km); positive into the ground 961 - magnitude = earthquake moment magnitude (Mw) 962 - method = code for rupture simulation constraints 963 "A" = strike, dip, and rake for first nodal plane solution preferred 964 (optional "strike", "dip", and "rake" arguments are required) 965 Warning: not recommended 966 "B" = strike, dip, and rake for second nodal plane solution preferred 967 (optional "strike2", "dip2", and "rake2" arguments are required) 968 Warning: not recommended 969 "C" = strike, dip, and rake are known for two nodal planes, and neither is preferred 970 (optional "strike", "dip", "rake", "strike2", "dip2", and "rake2" arguments are required) 971 "D" = One nodal plane solution for strike, dip, and rake; randomize the strike and dip 972 (optional "strike", "dip", and "rake" arguments are required) 973 Warning: not recommended 974 "E" = No nodal plane solutions; randomize strike, dip, and rake 975 (dip and rake are assigned based on faulting mechanism) 976 (if optional "mechanism" argument is not speficied, simulations randomly assign one) 977 - nsims = Number of simulations assigned to each M-scaling relationship. Total number of simulations should be odd. 978 nsims[0] = Wells & Coppersmith (1994) - [recommended 334] 979 nsims[1] = Leonard (2014) - [recommended 333] 980 nsims[2] = Thingbaijam et al. (2017) [recommended 333] 981 nsims[3] = Chiou & Youngs (2008) aspect ratio model with Wells & Coppersmith (1994) area relationship [recommended 111] 982 nsims[4] = Chiou & Youngs (2008) aspect ratio model with Leonard (2014) area relationship [recommended 111] 983 nsims[5] = Chiou & Youngs (2008) aspect ratio model with Thingbaijam et al. (2017) area relationship [recommended 111] 984 nsims[6] = Contreras et al. (2022) - [recommended 333] 985 - mechanim = known or preferred style-of-faulting [default None] 986 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 987 "NM" = normal: -150 < rake < -30 988 "RV" = reverse: 30 < rake < 150 989 - strike = strike-angle (degrees) of the first nodal plane solution [default None] 990 - dip = dip-angle (degrees) of the first nodal plane solution [default None] 991 - rake = rake-angle (degrees) of the first nodal plane solution [default None] 992 - strike2 = strike-angle (degrees) of the second nodal plane solution [default None] 993 - dip2 = dip-angle (degrees) of the second nodal plane solution [default None] 994 - rake2 = rake-angle (degrees) of the second nodal plane solution [default None] 995 996 Output Arguments: 997 - median_simulation = index of selected (median) rupture 998 - along_strikes = numpy array of the hypocenter along-strike relative position [0, 1] 999 - down_dips = numpy array of the hypocenter down-dip relative position [0, 1] 1000 - lon1s = numpy array of longitude (degrees) for the ULC rupture vertex 1001 - lat1s = numpy array of latitude (degrees) for the ULC rupture vertex 1002 - rpz1s = numpy array of depth (km) for the ULC rupture vertex 1003 - lon2s = numpy array of longitude (degrees) for the URC rupture vertex 1004 - lat2s = numpy array of latitude (degrees) for the URC rupture vertex 1005 - rpz2s = numpy array of depth (km) for the URC rupture vertex 1006 - lon3s = numpy array of longitude (degrees) for the LLC rupture vertex 1007 - lat3s = numpy array of latitude (degrees) for the LLC rupture vertex 1008 - rpz3s = numpy array of depth (km) for the LLC rupture vertex 1009 - lon4s = numpy array of longitude (degrees) for the LRC rupture vertex 1010 - lat4s = numpy array of latitude (degrees) for the LRC rupture vertex 1011 - rpz4s = numpy array of depth (km) for the LRC rupture vertex 1012 - strikes = numpy array of strike angle (degrees) 1013 - dips = numpy array of dip angle (degrees) 1014 - rakes = numpy array of rake angle (degrees) 1015 - models = numpy array of magnitude-scaling relationship used for each realization 1016 - areas = numpy array of area (km^2) 1017 - aspect_ratios = numpy array of aspect ratio 1018 - lengths = numpy array of rupture length (km) 1019 - widths = numpy array of rupture width (km) 1020 - top_depths = numpy array of top depth (km) of rupture 1021 - bottom_depths = numpy array of bottom depth (km) of rupture 1022 """ 1023 1024 check_input_arguments( 1025 eqType, method, nsims, strike, dip, rake, strike2, dip2, rake2 1026 ) 1027 try: 1028 strike = float(strike) 1029 dip = float(dip) 1030 rake = float(rake) 1031 except: 1032 pass 1033 try: 1034 strike2 = float(strike2) 1035 dip2 = float(dip2) 1036 rake2 = float(rake2) 1037 except: 1038 pass 1039 1040 # Set random seed ------------------------------------------------------------------------------------------------------- 1041 np.random.seed(seed=eqid) 1042 total_sims = np.sum(nsims) 1043 1044 # Set earthquake parameters -------------------------------------------------------------------------------------------- 1045 if method == "A": 1046 # Prefer the first nodal plane solution: 1047 strikes = np.repeat([strike], total_sims) 1048 dips = np.repeat([dip], total_sims) 1049 rakes = np.repeat([rake], total_sims) 1050 mechanisms = np.repeat(get_mechanism_based_on_rake(rake), total_sims) 1051 elif method == "B": 1052 # Prefer the second nodal plane solution: 1053 strikes = np.repeat([strike2], total_sims) 1054 dips = np.repeat([dip2], total_sims) 1055 rakes = np.repeat([rake2], total_sims) 1056 mechanisms = np.repeat(get_mechanism_based_on_rake(rake2), total_sims) 1057 elif method == "C": 1058 # Randomize between the two nodal plane solutions: 1059 nodal_planes = np.random.choice([1, 2], total_sims) 1060 strikes = np.where(nodal_planes == 1, strike, strike2) 1061 dips = np.where(nodal_planes == 1, dip, dip2) 1062 rakes = np.where(nodal_planes == 1, rake, rake2) 1063 mechanisms = v_get_mechanism_based_on_rake(rakes) 1064 elif method == "D": 1065 # Randomize strike and dip with a uniform distributions centered around the perscribed values 1066 strikes = np.random.uniform( 1067 strike - 30.0, strike + 30.0, total_sims 1068 ) # +/- 30 degrees 1069 strikes = np.where(strikes < 0, strikes + 360, strikes) # strike >= 0 1070 strikes = np.where(strikes >= 360.0, strikes - 360.0, strikes) # strike < 360 1071 dips = np.random.uniform(dip - 10.0, dip + 10.0, total_sims) # +/- 10 degrees 1072 dips = np.where(dips < 10.0, 10.0, dips) # 10 <= dip 1073 dips = np.where( 1074 dips > 89.9999999, 89.9999999, dips 1075 ) # dip <= 90 # cannot divide by zero 1076 rakes = np.repeat([rake], total_sims) 1077 mechanisms = v_get_mechanism_based_on_rake(rakes) 1078 elif method == "E": 1079 # Randomize everything based on prescribed mechanism 1080 # if mechanism is not prescribed, then randomize everything 1081 if mechanism == None: 1082 mechanisms = np.random.choice(["SS", "NM", "RV"], total_sims) 1083 else: 1084 mechanisms = np.repeat(mechanism, total_sims) 1085 strikes = np.random.uniform(0.0, 360.0, total_sims) 1086 strikes = np.where( 1087 strikes >= 360.0, strikes - 360.0, strikes 1088 ) # 0 <= strike < 360 1089 rakes = np.where(mechanisms == "SS", 0.0, mechanisms) 1090 rakes = np.where(rakes == "NM", -90.0, rakes) 1091 rakes = np.where(rakes == "RV", 90.0, rakes) 1092 dips = np.where( 1093 mechanisms == "SS", 89.9999999, mechanisms 1094 ) # cannot divide by zero 1095 dips = np.where(dips == "NM", 55.0, dips) 1096 dips = np.where(dips == "RV", 40.0, dips) 1097 dips = dips.astype(float) 1098 dips = np.where( 1099 dips > 89.9999999, 89.9999999, dips 1100 ) # dip <= 90 # cannot divide by zero 1101 1102 # Convert degrees to radians ------------------------------------------------------------------------------------------- 1103 strikes *= np.pi / 180.0 1104 dips *= np.pi / 180.0 1105 1106 # Simulate hypocenter down-dip and along-strike position --------------------------------------------------------------- 1107 down_dips = v_get_hyp_down_dip_position(np.repeat(eqType, total_sims), region) 1108 along_strikes = v_get_hyp_along_strike_position(np.repeat(eqType, total_sims)) 1109 1110 # Simulate rupture geometry using magnitude-scaling relationships ------------------------------------------------------ 1111 if nsims[0] > 0: 1112 nsim_WellsCoppersmith1994 = nsims[0] 1113 relationships_WellsCoppersmith1994 = v_WellsCoppersmith1994( 1114 np.repeat(magnitude, nsim_WellsCoppersmith1994), 1115 np.repeat(eqType, nsim_WellsCoppersmith1994), 1116 ) 1117 areas_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[0] 1118 aspect_ratios_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[1] 1119 lengths_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[2] 1120 widths_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[3] 1121 models = np.repeat("WellsCoppersmith1994", nsim_WellsCoppersmith1994) 1122 else: 1123 areas_WellsCoppersmith1994, aspect_ratios_WellsCoppersmith1994 = np.array( 1124 [] 1125 ), np.array([]) 1126 lengths_WellsCoppersmith1994, widths_WellsCoppersmith1994 = np.array( 1127 [] 1128 ), np.array([]) 1129 models = np.array([]) 1130 1131 if nsims[1] > 0: 1132 nsim_Leonard2014 = nsims[1] 1133 mechanisms_Leonard2014 = mechanisms[np.sum(nsims[:1]) : np.sum(nsims[:2])] 1134 relationships_Leonard2014 = v_Leonard2014( 1135 np.repeat(magnitude, nsim_Leonard2014), 1136 mechanisms_Leonard2014, 1137 np.repeat(eqType, nsim_Leonard2014), 1138 ) 1139 areas_Leonard2014 = relationships_Leonard2014[0] 1140 aspect_ratios_Leonard2014 = relationships_Leonard2014[1] 1141 lengths_Leonard2014 = relationships_Leonard2014[2] 1142 widths_Leonard2014 = relationships_Leonard2014[3] 1143 models = np.concatenate((models, np.repeat("Leonard2014", nsim_Leonard2014))) 1144 else: 1145 areas_Leonard2014, aspect_ratios_Leonard2014 = np.array([]), np.array([]) 1146 lengths_Leonard2014, widths_Leonard2014 = np.array([]), np.array([]) 1147 1148 if nsims[2] > 0: 1149 nsim_ThingbaijamEtAl2017 = nsims[2] 1150 mechanisms_ThingbaijamEtAl2017 = mechanisms[ 1151 np.sum(nsims[:2]) : np.sum(nsims[:3]) 1152 ] 1153 relationships_ThingbaijamEtAl2017 = v_ThingbaijamEtAl2017( 1154 np.repeat(magnitude, nsim_ThingbaijamEtAl2017), 1155 mechanisms_ThingbaijamEtAl2017, 1156 np.repeat(eqType, nsim_ThingbaijamEtAl2017), 1157 ) 1158 areas_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[0] 1159 aspect_ratios_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[1] 1160 lengths_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[2] 1161 widths_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[3] 1162 models = np.concatenate( 1163 (models, np.repeat("ThingbaijamEtAl2017", nsim_ThingbaijamEtAl2017)) 1164 ) 1165 else: 1166 areas_ThingbaijamEtAl2017, aspect_ratios_ThingbaijamEtAl2017 = np.array( 1167 [] 1168 ), np.array([]) 1169 lengths_ThingbaijamEtAl2017, widths_ThingbaijamEtAl2017 = np.array( 1170 [] 1171 ), np.array([]) 1172 1173 if nsims[3] > 0: 1174 nsim_ChiouYoungs2008_WellsCoppersmith1994 = nsims[3] 1175 mechanisms_ChiouYoungs2008_WellsCoppersmith1994 = mechanisms[ 1176 np.sum(nsims[:3]) : np.sum(nsims[:4]) 1177 ] 1178 relationships_ChiouYoungs2008_WellsCoppersmith1994 = v_ChiouYoungs2008( 1179 np.repeat(magnitude, nsim_ChiouYoungs2008_WellsCoppersmith1994), 1180 mechanisms_ChiouYoungs2008_WellsCoppersmith1994, 1181 np.repeat(eqType, nsim_ChiouYoungs2008_WellsCoppersmith1994), 1182 "WellsCoppersmith1994", 1183 ) 1184 areas_ChiouYoungs2008_WellsCoppersmith1994 = ( 1185 relationships_ChiouYoungs2008_WellsCoppersmith1994[0] 1186 ) 1187 aspect_ratios_ChiouYoungs2008_WellsCoppersmith1994 = ( 1188 relationships_ChiouYoungs2008_WellsCoppersmith1994[1] 1189 ) 1190 lengths_ChiouYoungs2008_WellsCoppersmith1994 = ( 1191 relationships_ChiouYoungs2008_WellsCoppersmith1994[2] 1192 ) 1193 widths_ChiouYoungs2008_WellsCoppersmith1994 = ( 1194 relationships_ChiouYoungs2008_WellsCoppersmith1994[3] 1195 ) 1196 models = np.concatenate( 1197 ( 1198 models, 1199 np.repeat( 1200 "ChiouYoungs2008_WellsCoppersmith1994", 1201 nsim_ChiouYoungs2008_WellsCoppersmith1994, 1202 ), 1203 ) 1204 ) 1205 else: 1206 ( 1207 areas_ChiouYoungs2008_WellsCoppersmith1994, 1208 aspect_ratios_ChiouYoungs2008_WellsCoppersmith1994, 1209 ) = np.array([]), np.array([]) 1210 ( 1211 lengths_ChiouYoungs2008_WellsCoppersmith1994, 1212 widths_ChiouYoungs2008_WellsCoppersmith1994, 1213 ) = np.array([]), np.array([]) 1214 1215 if nsims[4] > 0: 1216 nsim_ChiouYoungs2008_Leonard2014 = nsims[4] 1217 mechanisms_ChiouYoungs2008_Leonard2014 = mechanisms[ 1218 np.sum(nsims[:4]) : np.sum(nsims[:5]) 1219 ] 1220 relationships_ChiouYoungs2008_Leonard2014 = v_ChiouYoungs2008( 1221 np.repeat(magnitude, nsim_ChiouYoungs2008_Leonard2014), 1222 mechanisms_ChiouYoungs2008_Leonard2014, 1223 np.repeat(eqType, nsim_ChiouYoungs2008_Leonard2014), 1224 "Leonard2014", 1225 ) 1226 areas_ChiouYoungs2008_Leonard2014 = relationships_ChiouYoungs2008_Leonard2014[0] 1227 aspect_ratios_ChiouYoungs2008_Leonard2014 = ( 1228 relationships_ChiouYoungs2008_Leonard2014[1] 1229 ) 1230 lengths_ChiouYoungs2008_Leonard2014 = relationships_ChiouYoungs2008_Leonard2014[ 1231 2 1232 ] 1233 widths_ChiouYoungs2008_Leonard2014 = relationships_ChiouYoungs2008_Leonard2014[ 1234 3 1235 ] 1236 models = np.concatenate( 1237 ( 1238 models, 1239 np.repeat( 1240 "ChiouYoungs2008_Leonard2014", nsim_ChiouYoungs2008_Leonard2014 1241 ), 1242 ) 1243 ) 1244 else: 1245 areas_ChiouYoungs2008_Leonard2014, aspect_ratios_ChiouYoungs2008_Leonard2014 = ( 1246 np.array([]), 1247 np.array([]), 1248 ) 1249 lengths_ChiouYoungs2008_Leonard2014, widths_ChiouYoungs2008_Leonard2014 = ( 1250 np.array([]), 1251 np.array([]), 1252 ) 1253 1254 if nsims[5] > 0: 1255 nsim_ChiouYoungs2008_ThingbaijamEtAl2017 = nsims[5] 1256 mechanisms_ChiouYoungs2008_ThingbaijamEtAl2017 = mechanisms[ 1257 np.sum(nsims[:5]) : np.sum(nsims[:6]) 1258 ] 1259 relationships_ChiouYoungs2008_ThingbaijamEtAl2017 = v_ChiouYoungs2008( 1260 np.repeat(magnitude, nsim_ChiouYoungs2008_ThingbaijamEtAl2017), 1261 mechanisms_ChiouYoungs2008_ThingbaijamEtAl2017, 1262 np.repeat(eqType, nsim_ChiouYoungs2008_ThingbaijamEtAl2017), 1263 "ThingbaijamEtAl2017", 1264 ) 1265 areas_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1266 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[0] 1267 ) 1268 aspect_ratios_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1269 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[1] 1270 ) 1271 lengths_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1272 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[2] 1273 ) 1274 widths_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1275 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[3] 1276 ) 1277 models = np.concatenate( 1278 ( 1279 models, 1280 np.repeat( 1281 "ChiouYoungs2008_ThingbaijamEtAl2017", 1282 nsim_ChiouYoungs2008_ThingbaijamEtAl2017, 1283 ), 1284 ) 1285 ) 1286 else: 1287 ( 1288 areas_ChiouYoungs2008_ThingbaijamEtAl2017, 1289 aspect_ratios_ChiouYoungs2008_ThingbaijamEtAl2017, 1290 ) = np.array([]), np.array([]) 1291 ( 1292 lengths_ChiouYoungs2008_ThingbaijamEtAl2017, 1293 widths_ChiouYoungs2008_ThingbaijamEtAl2017, 1294 ) = np.array([]), np.array([]) 1295 1296 if nsims[6] > 0: 1297 nsim_ContrerasEtAl2022 = nsims[6] 1298 mechanisms_ContrerasEtAl2022 = mechanisms[np.sum(nsims[:6]) : np.sum(nsims[:7])] 1299 relationships_ContrerasEtAl2022 = v_ContrerasEtAl2022( 1300 np.repeat(magnitude, nsim_ContrerasEtAl2022), 1301 np.repeat(eqType, nsim_ContrerasEtAl2022), 1302 ) 1303 areas_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[0] 1304 aspect_ratios_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[1] 1305 lengths_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[2] 1306 widths_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[3] 1307 models = np.concatenate( 1308 (models, np.repeat("ContrerasEtAl2022", nsim_ContrerasEtAl2022)) 1309 ) 1310 else: 1311 areas_ContrerasEtAl2022, aspect_ratios_ContrerasEtAl2022 = np.array( 1312 [] 1313 ), np.array([]) 1314 lengths_ContrerasEtAl2022, widths_ContrerasEtAl2022 = np.array([]), np.array([]) 1315 1316 areas = np.concatenate( 1317 ( 1318 areas_WellsCoppersmith1994, 1319 areas_Leonard2014, 1320 areas_ThingbaijamEtAl2017, 1321 areas_ChiouYoungs2008_WellsCoppersmith1994, 1322 areas_ChiouYoungs2008_Leonard2014, 1323 areas_ChiouYoungs2008_ThingbaijamEtAl2017, 1324 areas_ContrerasEtAl2022, 1325 ) 1326 ) 1327 aspect_ratios = np.concatenate( 1328 ( 1329 aspect_ratios_WellsCoppersmith1994, 1330 aspect_ratios_Leonard2014, 1331 aspect_ratios_ThingbaijamEtAl2017, 1332 aspect_ratios_ChiouYoungs2008_WellsCoppersmith1994, 1333 aspect_ratios_ChiouYoungs2008_Leonard2014, 1334 aspect_ratios_ChiouYoungs2008_ThingbaijamEtAl2017, 1335 aspect_ratios_ContrerasEtAl2022, 1336 ) 1337 ) 1338 lengths = np.concatenate( 1339 ( 1340 lengths_WellsCoppersmith1994, 1341 lengths_Leonard2014, 1342 lengths_ThingbaijamEtAl2017, 1343 lengths_ChiouYoungs2008_WellsCoppersmith1994, 1344 lengths_ChiouYoungs2008_Leonard2014, 1345 lengths_ChiouYoungs2008_ThingbaijamEtAl2017, 1346 lengths_ContrerasEtAl2022, 1347 ) 1348 ) 1349 widths = np.concatenate( 1350 ( 1351 widths_WellsCoppersmith1994, 1352 widths_Leonard2014, 1353 widths_ThingbaijamEtAl2017, 1354 widths_ChiouYoungs2008_WellsCoppersmith1994, 1355 widths_ChiouYoungs2008_Leonard2014, 1356 widths_ChiouYoungs2008_ThingbaijamEtAl2017, 1357 widths_ContrerasEtAl2022, 1358 ) 1359 ) 1360 1361 # Compute geometric parameters ----------------------------------------------------------------------------------------- 1362 xf = np.sin(strikes) * lengths * along_strikes 1363 yf = np.cos(strikes) * lengths * along_strikes 1364 xb = np.sin(strikes + np.pi) * lengths * (1.0 - along_strikes) 1365 yb = np.cos(strikes + np.pi) * lengths * (1.0 - along_strikes) 1366 rwh = widths * np.cos(dips) 1367 rwv = widths * np.sin(dips) 1368 top_depths = hypd - rwv * down_dips 1369 down_dips = np.where( 1370 top_depths < 0.0, hypd / rwv, down_dips 1371 ) # make sure hypocenter is in the ground 1372 top_depths = np.where(top_depths < 0.0, 0.0, top_depths) # top_depth > 0.0 1373 bottom_depths = top_depths + rwv 1374 1375 # Compute top points of the rupture surface 1 = ULC & 2 = URC ---------------------------------------------------------- 1376 rpx1s = xf + np.sin(strikes - np.arcsin(1.0)) * rwh * down_dips 1377 rpy1s = yf + np.cos(strikes - np.arcsin(1.0)) * rwh * down_dips 1378 rpz1s = top_depths 1379 1380 rpx2s = xb + np.sin(strikes - np.arcsin(1.0)) * rwh * down_dips 1381 rpy2s = yb + np.cos(strikes - np.arcsin(1.0)) * rwh * down_dips 1382 rpz2s = top_depths 1383 1384 # Compute bottom points of the rupture surface 3 = LRC & 4 = LLC ------------------------------------------------------- 1385 rpx3s = xf + np.sin(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1386 rpy3s = yf + np.cos(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1387 rpz3s = bottom_depths 1388 1389 rpx4s = xb + np.sin(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1390 rpy4s = yb + np.cos(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1391 rpz4s = bottom_depths 1392 1393 # Create a psudo-grid of stations -------------------------------------------------------------------------------------- 1394 r = np.array( 1395 list(np.arange(2, 20, 2)) 1396 + list(np.arange(25, 55, 5)) 1397 + list(np.arange(60, 110, 10)) 1398 + list(np.arange(125, 325, 25)) 1399 ) 1400 theta = np.pi * np.linspace(0, 360, 25)[:-1] / 180 1401 psx = np.multiply( 1402 np.repeat(r.reshape(1, len(r)), len(theta), axis=0), 1403 np.sin(theta.reshape(len(theta), 1)), 1404 ).flatten() 1405 psy = np.multiply( 1406 np.repeat(r.reshape(1, len(r)), len(theta), axis=0), 1407 np.cos(theta.reshape(len(theta), 1)), 1408 ).flatten() 1409 1410 # Compute distances to each psudo-station ------------------------------------------------------------------------------ 1411 SITES = np.array([[[x, y, 0]] for x, y in zip(psx, psy)]) 1412 TRI_123 = np.array( 1413 [ 1414 [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] 1415 for x1, x2, x3, y1, y2, y3, z1, z2, z3 in zip( 1416 rpx1s, rpx2s, rpx3s, rpy1s, rpy2s, rpy3s, rpz1s, rpz2s, rpz3s 1417 ) 1418 ] 1419 ) 1420 TRI_234 = np.array( 1421 [ 1422 [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] 1423 for x1, x2, x3, y1, y2, y3, z1, z2, z3 in zip( 1424 rpx2s, rpx4s, rpx3s, rpy2s, rpy4s, rpy3s, rpz2s, rpz4s, rpz3s 1425 ) 1426 ] 1427 ) 1428 rrups1 = pointTriangleDistance(TRI_123, SITES) 1429 rrups2 = pointTriangleDistance(TRI_234, SITES) 1430 rrups = np.minimum(rrups1, rrups2) 1431 1432 # Locate the median Rrup for each site --------------------------------------------------------------------------------- 1433 median_rrups = np.median(rrups, axis=0) 1434 median_rrup_indices = v_get_median_index(np.transpose(rrups)) 1435 1436 # Locate the simulation that minimizes the difference between median Rrup and Rrup across all sites -------------------- 1437 sum_of_difference_squared = np.sum((median_rrups - rrups) ** 2, axis=1) 1438 median_simulation = np.argmin(sum_of_difference_squared) 1439 1440 # Convert simulated faults xy to ll using haversine formulation -------------------------------------------------------- 1441 lon1s, lat1s = v_XY2LL(elon, elat, rpx1s, rpy1s) 1442 lon2s, lat2s = v_XY2LL(elon, elat, rpx2s, rpy2s) 1443 lon3s, lat3s = v_XY2LL(elon, elat, rpx3s, rpy3s) 1444 lon4s, lat4s = v_XY2LL(elon, elat, rpx4s, rpy4s) 1445 1446 # Convert radians to degrees ------------------------------------------------------------------------------------------- 1447 strikes = np.round(strikes * 180 / np.pi, 6) 1448 dips = np.round(dips * 180 / np.pi, 6) 1449 1450 # Return everything ---------------------------------------------------------------------------------------------------- 1451 return ( 1452 median_simulation, 1453 along_strikes, 1454 down_dips, 1455 lon1s, 1456 lat1s, 1457 rpz1s, 1458 lon2s, 1459 lat2s, 1460 rpz2s, 1461 lon3s, 1462 lat3s, 1463 rpz3s, 1464 lon4s, 1465 lat4s, 1466 rpz4s, 1467 strikes, 1468 dips, 1469 rakes, 1470 models, 1471 areas, 1472 aspect_ratios, 1473 lengths, 1474 widths, 1475 top_depths, 1476 bottom_depths, 1477 ) 1478 1479 1480def simulate_rupture_surface( 1481 eqid, 1482 eqType, 1483 region, 1484 elat, 1485 elon, 1486 hypd, 1487 magnitude, 1488 method, 1489 nsims, 1490 mechanism=None, 1491 strike=None, 1492 dip=None, 1493 rake=None, 1494 strike2=None, 1495 dip2=None, 1496 rake2=None, 1497): 1498 """ 1499 Simulate earthquake rupture surface that minimizes the difference between the median distance of a pseudo-grid of sites 1500 and a stochastic set of possible ruptures. Randomized fault attributes are dependent on the fault category. Fault scaling 1501 models depend on earthquake type. Adapted from the CCLD routine originally coded in Frortran by Chiou and Youngs (2008). 1502 1503 Input Arguments: 1504 - eqid = unique integer identifier for the event (to set the random seed) 1505 - eqType = type of earthquake: 1506 "crustal" = shallow-crustal in active tectonic regimes 1507 "intraslab" = intraslab-type in subduction regimes 1508 "interface" = interface-type in subduction regimes 1509 "stable" = shallow-crustal in stable-continental regimes 1510 - region = geographic region where the earthquake occured" 1511 "japan" 1512 "chile" 1513 "other" 1514 - elat = hypocenter latitude (degrees) 1515 - elon = hypocenter longiude (degrees) 1516 - hypd = hypocenter depth (km); positive into the ground 1517 - magnitude = earthquake moment magnitude (Mw) 1518 - method = code for rupture simulation constraints 1519 "A" = strike, dip, and rake for first nodal plane solution preferred 1520 (optional "strike", "dip", and "rake" arguments are required) 1521 Warning: not recommended 1522 "B" = strike, dip, and rake for second nodal plane solution preferred 1523 (optional "strike2", "dip2", and "rake2" arguments are required) 1524 Warning: not recommended 1525 "C" = strike, dip, and rake are known for two nodal planes, and neither is preferred 1526 (optional "strike", "dip", "rake", "strike2", "dip2", and "rake2" arguments are required) 1527 "D" = One nodal plane solution for strike, dip, and rake; randomize the strike and dip 1528 (optional "strike", "dip", and "rake" arguments are required) 1529 Warning: not recommended 1530 "E" = No nodal plane solutions; randomize strike, dip, and rake 1531 (dip and rake are assigned based on faulting mechanism) 1532 (if optional "mechanism" argument is not speficied, simulations randomly assign one) 1533 - nsims = Number of simulations assigned to each M-scaling relationship. Total number of simulations should be odd. 1534 nsims[0] = Wells & Coppersmith (1994) - [recommended 334] 1535 nsims[1] = Leonard (2014) - [recommended 333] 1536 nsims[2] = Thingbaijam et al. (2017) [recommended 333] 1537 nsims[3] = Chiou & Youngs (2008) aspect ratio model with Wells & Coppersmith (1994) area relationship [recommended 111] 1538 nsims[4] = Chiou & Youngs (2008) aspect ratio model with Leonard (2014) area relationship [recommended 111] 1539 nsims[5] = Chiou & Youngs (2008) aspect ratio model with Thingbaijam et al. (2017) area relationship [recommended 111] 1540 nsims[6] = Contreras et al. (2022) - [recommended 333] 1541 - mechanim = known or preferred style-of-faulting [default None] 1542 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 1543 "NM" = normal: -150 < rake < -30 1544 "RV" = reverse: 30 < rake < 150 1545 - strike = strike-angle (degrees) of the first nodal plane solution [default None] 1546 - dip = dip-angle (degrees) of the first nodal plane solution [default None] 1547 - rake = rake-angle (degrees) of the first nodal plane solution [default None] 1548 - strike2 = strike-angle (degrees) of the second nodal plane solution [default None] 1549 - dip2 = dip-angle (degrees) of the second nodal plane solution [default None] 1550 - rake2 = rake-angle (degrees) of the second nodal plane solution [default None] 1551 1552 Output Objects: 1553 - SIMULATIONS = pandas DataFrame object containing all simulated rupture surfaces 1554 - SELECTED = pandas DataFrame object containing the selected rupture surface and statistics 1555 """ 1556 1557 ( 1558 median_simulation, 1559 along_strikes, 1560 down_dips, 1561 lon1s, 1562 lat1s, 1563 rpz1s, 1564 lon2s, 1565 lat2s, 1566 rpz2s, 1567 lon3s, 1568 lat3s, 1569 rpz3s, 1570 lon4s, 1571 lat4s, 1572 rpz4s, 1573 strikes, 1574 dips, 1575 rakes, 1576 models, 1577 areas, 1578 aspect_ratios, 1579 lengths, 1580 widths, 1581 top_depths, 1582 bottom_depths, 1583 ) = get_rupture_surface_simulations( 1584 eqid, 1585 eqType, 1586 region, 1587 elat, 1588 elon, 1589 hypd, 1590 magnitude, 1591 method, 1592 nsims, 1593 mechanism, 1594 strike, 1595 dip, 1596 rake, 1597 strike2, 1598 dip2, 1599 rake2, 1600 ) 1601 1602 # Compute the statistics of the simulation results ---------------------------------------------------------------------- 1603 areas_avg = 10 ** np.mean(np.log10(areas)) 1604 areas_std = 10 ** np.std(np.log10(areas)) 1605 aspect_ratios_avg = 10 ** np.mean(np.log10(aspect_ratios)) 1606 aspect_ratios_std = 10 ** np.std(np.log10(aspect_ratios)) 1607 lengths_avg = 10 ** np.mean(np.log10(lengths)) 1608 lengths_std = 10 ** np.std(np.log10(lengths)) 1609 widths_avg = 10 ** np.mean(np.log10(widths)) 1610 widths_std = 10 ** np.std(np.log10(widths)) 1611 1612 max_top_depths = np.max(top_depths) 1613 min_top_depths = np.min(top_depths) 1614 max_bottom_depths = np.max(bottom_depths) 1615 min_bottom_depths = np.min(bottom_depths) 1616 1617 # Construct pandas DataFrame objects to package the results ------------------------------------------------------------- 1618 total_sims = np.sum(nsims) 1619 SIMULATIONS = pd.DataFrame( 1620 { 1621 "Simulation": np.array(range(total_sims)) + 1, 1622 "EQID": np.repeat(eqid, total_sims), 1623 "Magnitude": np.repeat(magnitude, total_sims), 1624 "Hypocenter Longitude": np.repeat(elon, total_sims), 1625 "Hypocenter Latitude": np.repeat(elat, total_sims), 1626 "Hypocenter Depth (km)": np.repeat(hypd, total_sims), 1627 "Hypocenter Along-Strike Position": along_strikes, 1628 "Hypocenter Down-Dip Position": down_dips, 1629 "ULC Longitude": lon2s, 1630 "ULC Latitude": lat2s, 1631 "ULC Depth (km)": rpz2s, 1632 "URC Longitude": lon1s, 1633 "URC Latitude": lat1s, 1634 "URC Depth (km)": rpz1s, 1635 "LRC Longitude": lon3s, 1636 "LRC Latitude": lat3s, 1637 "LRC Depth (km)": rpz3s, 1638 "LLC Longitude": lon4s, 1639 "LLC Latitude": lat4s, 1640 "LLC Depth (km)": rpz4s, 1641 "Strike": strikes, 1642 "Dip": dips, 1643 "Rake": rakes, 1644 "Scaling Relation": models, 1645 "Area (km^2)": areas, 1646 "Aspect Ratio": aspect_ratios, 1647 "Rupture Length (km)": lengths, 1648 "Rupture Width (km)": widths, 1649 "Rupture Top Depth (km)": top_depths, 1650 "Rupture Bottom Depth (km)": bottom_depths, 1651 } 1652 ) 1653 1654 SELECTED = pd.DataFrame( 1655 { 1656 "Median Simulation": [median_simulation + 1], 1657 "EQID": [eqid], 1658 "Magnitude": [magnitude], 1659 "Hypocenter Longitude": [elon], 1660 "Hypocenter Latitude": [elat], 1661 "Hypocenter Depth (km)": [hypd], 1662 "Hypocenter Along-Strike Position": [along_strikes[median_simulation]], 1663 "Hypocenter Down-Dip Position": [down_dips[median_simulation]], 1664 "ULC Longitude": [lon2s[median_simulation]], 1665 "ULC Latitude": [lat2s[median_simulation]], 1666 "ULC Depth (km)": [rpz2s[median_simulation]], 1667 "URC Longitude": [lon1s[median_simulation]], 1668 "URC Latitude": [lat1s[median_simulation]], 1669 "URC Depth (km)": [rpz1s[median_simulation]], 1670 "LRC Longitude": [lon3s[median_simulation]], 1671 "LRC Latitude": [lat3s[median_simulation]], 1672 "LRC Depth (km)": [rpz3s[median_simulation]], 1673 "LLC Longitude": [lon4s[median_simulation]], 1674 "LLC Latitude": [lat4s[median_simulation]], 1675 "LLC Depth (km)": [rpz4s[median_simulation]], 1676 "Strike": [strikes[median_simulation]], 1677 "Dip": [dips[median_simulation]], 1678 "Rake": [rakes[median_simulation]], 1679 "Scaling Relation": [models[median_simulation]], 1680 "Area (km^2)": [areas[median_simulation]], 1681 "Average Area (km^2)": [areas_avg], 1682 "Area Standard Deviation (km^2)": [areas_std], 1683 "Aspect Ratio": [aspect_ratios[median_simulation]], 1684 "Average Aspect Ratio": [aspect_ratios_avg], 1685 "Aspect Ratio Standard Deviation": [aspect_ratios_std], 1686 "Length (km)": [lengths[median_simulation]], 1687 "Average Length (km)": [lengths_avg], 1688 "Length Standard Deviation (km)": [lengths_std], 1689 "Width (km)": [widths[median_simulation]], 1690 "Average Width (km)": [widths_avg], 1691 "Width Standard Deviation (km)": [widths_std], 1692 "Rupture Top Depth (km)": [top_depths[median_simulation]], 1693 "Rupture Bottom Depth (km)": [bottom_depths[median_simulation]], 1694 } 1695 ) 1696 1697 return (SIMULATIONS, SELECTED)
26def WellsCoppersmith1994(magnitude, eqType): 27 """ 28 Magnitude-Scaling relationships defined by Wells & Coppersmith (1994) for all style-of-faultings. 29 30 Input Arguments: 31 - magnitude = moment magnitude (Mw) 32 - eqType = type of earthquake (tectonic regime) 33 "crustal" = shallow-crustal in active tectonic regimes 34 "intraslab" = intraslab-type in subduction regimes 35 "interface" = interface-type in subduction regimes 36 "stable" = shallow-crustal in stable-continental regimes 37 38 Output Arguments: 39 - A = rupture area (km^2) 40 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 41 - L = rupture along-strike length (km) 42 - W = rupture down-dip width (km) 43 """ 44 if eqType in ["intraslab", "interface", "stable"]: 45 raise ValueError( 46 "eqType: WellsCoppersmith1994 cannot be used for subduction-type or stable-continental events ('intraslab', 'interface', or 'stable')" 47 ) 48 return (np.nan, np.nan, np.nan, np.nan) 49 50 elif eqType == "crustal": 51 # Rupture Area: 52 a1 = -3.49 53 b1 = 0.91 54 s1 = 0.24 55 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 56 # Rupture Length: 57 a2 = -2.44 58 b2 = 0.59 59 s2 = 0.16 60 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 61 # Aspect Ratio: 62 AR = L**2 / A 63 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 64 if AR < 1.0: 65 # AR = 1.0 66 s_cy08 = 0.16 67 AR = np.random.normal(1, s_cy08) 68 L = np.sqrt(A * AR) 69 W = np.sqrt(A / AR) 70 else: 71 W = L / AR 72 return (A, AR, L, W)
Magnitude-Scaling relationships defined by Wells & Coppersmith (1994) for all style-of-faultings.
Input Arguments:
- magnitude = moment magnitude (Mw)
- eqType = type of earthquake (tectonic regime) "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
Output Arguments:
- A = rupture area (km^2)
- AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes
- L = rupture along-strike length (km)
- W = rupture down-dip width (km)
75def Leonard2014(magnitude, mechanism, eqType): 76 """ 77 Magnitude-Scaling relationships defined by Leonard (2014) for different tectonic regimes and style-of-faultings. 78 79 Input Arguments: 80 - magnitude = moment magnitude (Mw) 81 - mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) 82 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 83 "NM" = normal: -150 < rake < -30 84 "RV" = reverse: 30 < rake < 150 85 - eqType = type of earthquake (tectonic regime) 86 "crustal" = shallow-crustal in active tectonic regimes 87 "intraslab" = intraslab-type in subduction regimes 88 "interface" = interface-type in subduction regimes 89 "stable" = shallow-crustal in stable-continental regimes 90 91 Output Arguments: 92 - A = rupture area (km^2) 93 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 94 - L = rupture along-strike length (km) 95 - W = rupture down-dip width (km) 96 """ 97 if eqType in ["intraslab", "interface"]: 98 raise ValueError( 99 "eqType: Leonard2014 cannot be used for subduction-type events ('intraslab' or 'interface')" 100 ) 101 return (np.nan, np.nan, np.nan, np.nan) 102 103 elif eqType == "crustal": 104 if mechanism == "SS": 105 # Rupture Area: 106 a1 = 3.99 107 b1 = 1.00 108 s1 = 0.13 109 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 110 # Rupture Length: 111 a2 = 4.170 112 b2 = 1.667 113 s2 = 0.19 114 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 115 if L > 45.0: 116 a3 = 5.27 117 b3 = 1.000 118 L = 10 ** ((magnitude - a3 - np.random.normal(0, s2)) / b3) 119 elif mechanism in ["NM", "RV"]: 120 # Rupture Area: 121 a1 = 4.00 122 b1 = 1.00 123 s1 = 0.15 124 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 125 # Rupture Length: 126 a2 = 4.000 127 b2 = 2.000 128 s2 = 0.23 129 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 130 if L > 5.4: 131 a3 = 4.240 132 b3 = 1.667 133 L = 10 ** ((magnitude - a3 - np.random.normal(0, s2)) / b3) 134 # Aspect Ratio: 135 AR = L**2 / A 136 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 137 if AR < 1.0: 138 # AR = 1.0 139 s_cy08 = 0.16 140 AR = np.random.normal(1, s_cy08) 141 L = np.sqrt(A * AR) 142 W = np.sqrt(A / AR) 143 else: 144 W = L / AR 145 return (A, AR, L, W) 146 147 elif eqType == "stable": 148 if mechanism == "SS": 149 # Rupture Area: 150 a1 = 4.18 151 b1 = 1.00 152 s1 = 0.09 153 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 154 # Rupture Length: 155 a2 = 4.250 156 b2 = 1.667 157 s2 = 0.18 158 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 159 if L > 60.0: 160 a3 = 5.44 161 b3 = 1.000 162 L = 10 ** ((magnitude - a3 - np.random.normal(0, s2)) / b3) 163 elif mechanism in ["NM", "RV"]: 164 # Rupture Area: 165 a1 = 4.19 166 b1 = 1.00 167 s1 = 0.10 168 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 169 # Rupture Length: 170 a2 = 4.320 171 b2 = 1.667 172 s2 = 0.19 173 L = 10 ** ((magnitude - a2 - np.random.normal(0, s2)) / b2) 174 # Aspect Ratio: 175 AR = L**2 / A 176 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 177 if AR < 1.0: 178 # AR = 1.0 179 s_cy08 = 0.16 180 AR = np.random.normal(1, s_cy08) 181 L = np.sqrt(A * AR) 182 W = np.sqrt(A / AR) 183 else: 184 W = L / AR 185 return (A, AR, L, W)
Magnitude-Scaling relationships defined by Leonard (2014) for different tectonic regimes and style-of-faultings.
Input Arguments:
- magnitude = moment magnitude (Mw)
- mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) "NM" = normal: -150 < rake < -30 "RV" = reverse: 30 < rake < 150
- eqType = type of earthquake (tectonic regime) "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
Output Arguments:
- A = rupture area (km^2)
- AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes
- L = rupture along-strike length (km)
- W = rupture down-dip width (km)
188def ThingbaijamEtAl2017(magnitude, mechanism, eqType): 189 """ 190 Magnitude-Scaling relationships defined by Thingbaijam et al. (2017) for different tectonic regimes and style-of-faultings. 191 192 Input Arguments: 193 - magnitude = moment magnitude (Mw) 194 - mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) 195 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 196 "NM" = normal: -150 < rake < -30 197 "RV" = reverse: 30 < rake < 150 198 - eqType = type of earthquake (tectonic regime) 199 "crustal" = shallow-crustal in active tectonic regimes 200 "intraslab" = intraslab-type in subduction regimes 201 "interface" = interface-type in subduction regimes 202 "stable" = shallow-crustal in stable-continental regimes 203 204 Output Arguments: 205 - A = rupture area (km^2) 206 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 207 - L = rupture along-strike length (km) 208 - W = rupture down-dip width (km) 209 """ 210 if eqType in ["intraslab", "stable"]: 211 raise ValueError( 212 "eqType: ThingbaijamEtAl2017 cannot be used for instra-slab or stable-continental events ('intraslab' or 'stable')" 213 ) 214 return (np.nan, np.nan, np.nan, np.nan) 215 216 elif eqType == "crustal": 217 if mechanism == "SS": 218 # Rupture Area: 219 a1 = -3.486 220 b1 = 0.942 221 s1 = 0.184 222 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 223 # Rupture Length: 224 a2 = -2.943 225 b2 = 0.681 226 s2 = 0.151 227 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 228 elif mechanism == "NM": 229 # Rupture Area: 230 a1 = -2.551 231 b1 = 0.808 232 s1 = 0.181 233 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 234 # Rupture Length: 235 a2 = -1.722 236 b2 = 0.485 237 s2 = 0.128 238 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 239 elif mechanism == "RV": 240 # Rupture Area: 241 a1 = -4.362 242 b1 = 1.049 243 s1 = 0.121 244 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 245 # Rupture Length: 246 a2 = -2.693 247 b2 = 0.614 248 s2 = 0.083 249 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 250 # Aspect Ratio: 251 AR = L**2 / A 252 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 253 if AR < 1.0: 254 # AR = 1.0 255 s_cy08 = 0.16 256 AR = np.random.normal(1, s_cy08) 257 L = np.sqrt(A * AR) 258 W = np.sqrt(A / AR) 259 else: 260 W = L / AR 261 return (A, AR, L, W) 262 263 elif eqType == "interface": 264 # Rupture Area: 265 a1 = -3.292 266 b1 = 0.949 267 s1 = 0.150 268 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 269 # Rupture Length: 270 a2 = -2.412 271 b2 = 0.583 272 s2 = 0.107 273 L = 10 ** (a2 + b2 * magnitude + np.random.normal(0, s2)) 274 # Aspect Ratio: 275 AR = L**2 / A 276 # Check if the Aspect Ratio < 1.0, if so constrain and derive L & W: 277 if AR < 1.0: 278 # AR = 1.0 279 s_cy08 = 0.16 280 AR = np.random.normal(1, s_cy08) 281 L = np.sqrt(A * AR) 282 W = np.sqrt(A / AR) 283 else: 284 W = L / AR 285 return (A, AR, L, W)
Magnitude-Scaling relationships defined by Thingbaijam et al. (2017) for different tectonic regimes and style-of-faultings.
Input Arguments:
- magnitude = moment magnitude (Mw)
- mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) "NM" = normal: -150 < rake < -30 "RV" = reverse: 30 < rake < 150
- eqType = type of earthquake (tectonic regime) "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
Output Arguments:
- A = rupture area (km^2)
- AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes
- L = rupture along-strike length (km)
- W = rupture down-dip width (km)
288def ChiouYoungs2008(magnitude, mechanism, eqType, model): 289 """ 290 Magnitude-Scaling aspect ratio relationship defined by Chiou & Youngs (2008) for different style-of-faultings 291 in combination with other published magnitude-scaling area relationships. 292 293 Input Arguments: 294 - magnitude = moment magnitude (Mw) 295 - mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) 296 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 297 "NM" = normal: -150 < rake < -30 298 "RV" = reverse: 30 < rake < 150 299 - eqType = type of earthquake (tectonic regime) 300 "crustal" = shallow-crustal in active tectonic regimes 301 "intraslab" = intraslab-type in subduction regimes 302 "interface" = interface-type in subduction regimes 303 "stable" = shallow-crustal in stable-continental regimes 304 - model = magntiude-scaling area relationship to use with Chiou & Youngs (2008) aspect ratio relation 305 "WellsCoppersmith1994" = Wells & Coppersmith (1994) 306 "Leonard2014" = Leonard (2014) 307 "ThingbaijamEtAl2017" = Thingbaijam et al. (2017) 308 309 Output Arguments: 310 - A = rupture area (km^2) 311 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 312 - L = rupture along-strike length (km) 313 - W = rupture down-dip width (km) 314 """ 315 if eqType in ["intraslab", "interface", "stable"]: 316 raise ValueError( 317 "eqType: ChiouYoungs2008 cannot be used for subduction-type or stable-continental events ('intraslab', 'interface', or 'stable')" 318 ) 319 return (np.nan, np.nan, np.nan, np.nan) 320 321 elif eqType == "crustal": 322 if model == "WellsCoppersmith1994": 323 # Rupture Area: 324 a1 = -3.49 325 b1 = 0.91 326 s1 = 0.24 327 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 328 elif model == "Leonard2014": 329 if mechanism == "SS": 330 # Rupture Area: 331 a1 = 3.99 332 b1 = 1.00 333 s1 = 0.13 334 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 335 elif mechanism in ["NM", "RV"]: 336 # Rupture Area: 337 a1 = 4.00 338 b1 = 1.00 339 s1 = 0.15 340 A = 10 ** ((magnitude - a1 - np.random.normal(0, s1)) / b1) 341 elif model == "ThingbaijamEtAl2017": 342 if mechanism == "SS": 343 # Rupture Area: 344 a1 = -3.486 345 b1 = 0.942 346 s1 = 0.184 347 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 348 elif mechanism == "NM": 349 # Rupture Area: 350 a1 = -2.551 351 b1 = 0.808 352 s1 = 0.181 353 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 354 elif mechanism == "RV": 355 # Rupture Area: 356 a1 = -4.362 357 b1 = 1.049 358 s1 = 0.121 359 A = 10 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 360 # Aspect Ratio: 361 s = 0.16 362 if magnitude < 4.0: 363 # AR = 1.0 364 AR = np.random.normal(1, s) 365 else: 366 FNM = 0.0 367 FRV = 0.0 368 if mechanism == "NM": 369 FNM = 1.0 370 elif mechanism == "RV": 371 FRV = 1.0 372 ass = 0.01752 373 anm = -0.00472 374 arv = -0.01099 375 b = 3.097 376 AR = 10 ** ( 377 (ass + anm * FNM + arv * FRV) * (magnitude - 4.0) ** b 378 + np.random.normal(0, s) 379 ) 380 # Compute Length and Width: 381 L = np.sqrt(A * AR) 382 W = np.sqrt(A / AR) 383 return (A, AR, L, W)
Magnitude-Scaling aspect ratio relationship defined by Chiou & Youngs (2008) for different style-of-faultings in combination with other published magnitude-scaling area relationships.
Input Arguments:
- magnitude = moment magnitude (Mw)
- mechanism = known or preferred style-of-faulting; can be inferred from rake angle (e.g., Ancheta et al. 2013) "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) "NM" = normal: -150 < rake < -30 "RV" = reverse: 30 < rake < 150
- eqType = type of earthquake (tectonic regime) "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
- model = magntiude-scaling area relationship to use with Chiou & Youngs (2008) aspect ratio relation "WellsCoppersmith1994" = Wells & Coppersmith (1994) "Leonard2014" = Leonard (2014) "ThingbaijamEtAl2017" = Thingbaijam et al. (2017)
Output Arguments:
- A = rupture area (km^2)
- AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes
- L = rupture along-strike length (km)
- W = rupture down-dip width (km)
386def ContrerasEtAl2022(magnitude, eqType): 387 """ 388 Magnitude-Scaling aspect ratio relationship defined by Contreras et al. (222) for subduction-type earthquakes. 389 390 Input Arguments: 391 - magnitude = moment magnitude (Mw) 392 - eqType = type of earthquake (tectonic regime) 393 "crustal" = shallow-crustal in active tectonic regimes 394 "intraslab" = intraslab-type in subduction regimes 395 "interface" = interface-type in subduction regimes 396 "stable" = shallow-crustal in stable-continental regimes 397 398 Output Arguments: 399 - A = rupture area (km^2) 400 - AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes 401 - L = rupture along-strike length (km) 402 - W = rupture down-dip width (km) 403 """ 404 if eqType in ["crustal", "stable"]: 405 raise ValueError( 406 "eqType: ContrerasEtAl2022 cannot be used for non-subduction-type events ('crustal' or 'stable')" 407 ) 408 return (np.nan, np.nan, np.nan, np.nan) 409 410 elif eqType == "interface": 411 # Rupture Area: 412 a1 = -3.8290 413 b1 = 1.0 414 s1 = 0.270 415 A = 10.0 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 416 # Aspect Ratio: 417 if magnitude > 7.25: 418 AR = 10.0 ** ( 419 0.2759 * (magnitude - 7.25) + np.random.normal(0, 0.192) 420 ) # std = 0.441 in ln unit 421 else: 422 AR = 10.00 ** (0.0 + np.random.normal(0, 0.0717)) # std = 0.165 in ln unit 423 # Compute Length and Width: 424 L = np.sqrt(A * AR) 425 W = np.sqrt(A / AR) 426 return (A, AR, L, W) 427 428 elif eqType == "intraslab": 429 # Rupture Area: 430 a1 = -3.251 431 b1 = 0.890 432 s1 = 0.184 433 A = 10.0 ** (a1 + b1 * magnitude + np.random.normal(0, s1)) 434 # Aspect Ratio: 435 if magnitude > 6.5: 436 AR = 10.0 ** ( 437 0.0938 * (magnitude - 6.5) + np.random.normal(0, 0.164) 438 ) # std = 0.378in ln unit 439 else: 440 AR = 10.00 ** (0.0 + np.random.normal(0, 0.104)) # std = 0.239 in ln unit 441 # Compute Length and Width: 442 L = np.sqrt(A * AR) 443 W = np.sqrt(A / AR) 444 return (A, AR, L, W)
Magnitude-Scaling aspect ratio relationship defined by Contreras et al. (222) for subduction-type earthquakes.
Input Arguments:
- magnitude = moment magnitude (Mw)
- eqType = type of earthquake (tectonic regime) "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
Output Arguments:
- A = rupture area (km^2)
- AR = rupture aspect ratio (L/W) - median constrained to be >= 1.0 when extrapilated to small magnitudes
- L = rupture along-strike length (km)
- W = rupture down-dip width (km)
447def get_mechanism_based_on_rake(rake): 448 """ 449 Infer style-of-faulting (mechanism) from rake angle (e.g., Ancheta et al. 2013) 450 451 Input Arguments: 452 rake = rake angle (degrees) 453 454 Output Arguments: 455 mechanism = indicator string for style-of-faulting (mechanism) 456 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 457 "NM" = normal: -150 < rake < -30 458 "RV" = reverse: 30 < rake < 150 459 """ 460 if (-180 <= rake < -150) | (-30 <= rake < 30) | (150 <= rake <= 180): 461 return "SS" 462 elif (-120 <= rake < -60) | (-150 <= rake < -120) | (-60 <= rake < -30): 463 return "NM" 464 elif (60 <= rake < 120) | (30 <= rake < 60) | (120 <= rake < 150): 465 return "RV"
Infer style-of-faulting (mechanism) from rake angle (e.g., Ancheta et al. 2013)
Input Arguments: rake = rake angle (degrees)
Output Arguments: mechanism = indicator string for style-of-faulting (mechanism) "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) "NM" = normal: -150 < rake < -30 "RV" = reverse: 30 < rake < 150
476def get_hyp_down_dip_position(eqtype, region): 477 """ 478 Simulate hypocenter's down-dip relative position on the rupture surface based on Chiou & Youngs (2008) 479 480 Input arguments: 481 - eqType = type of earthquake (tectonic regime) 482 "crustal" = shallow-crustal in active tectonic regimes 483 "intraslab" = intraslab-type in subduction regimes 484 "interface" = interface-type in subduction regimes 485 "stable" = shallow-crustal in stable-continental regimes 486 - region = geographic region where the earthquake occured 487 "japan" 488 "chile" 489 "other" 490 491 Output Arguments: 492 - fd = down-dip relative position of hypocenter on rupture surface [0, 1] 493 0 ~ along the top-depth of the rupture 494 1 ~ along the bottom-depth of the rupture 495 """ 496 nxf = 11 497 xdf = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.00]) 498 ssd = np.array([0, 0.025, 0.05, 0.1, 0.175, 0.275, 0.4, 0.55, 0.7, 0.85, 1.00]) 499 iad = np.array( 500 [0.00, 0.012, 0.051, 0.139, 0.294, 0.500, 0.706, 0.861, 0.949, 0.988, 1.00] 501 ) 502 ied0 = np.array( 503 [0.000, 0.024, 0.085, 0.206, 0.389, 0.599, 0.783, 0.906, 0.969, 0.993, 1.00] 504 ) 505 ied1 = np.array( 506 [0.0, 0.002, 0.012, 0.044, 0.121, 0.262, 0.460, 0.671, 0.843, 0.950, 1.00] 507 ) 508 ied2 = np.array( 509 [0.00, 0.013, 0.053, 0.143, 0.297, 0.500, 0.703, 0.857, 0.947, 0.987, 1.00] 510 ) 511 if eqtype in ["crustal", "stable"]: 512 fd = discrete(nxf, xdf, ssd, np.random.rand()) 513 elif eqtype == "intraslab": 514 fd = discrete(nxf, xdf, iad, np.random.rand()) 515 elif eqtype == "interface": 516 if region == "japan": 517 fd = discrete(nxf, xdf, ied0, np.random.rand()) 518 elif region == "chile": 519 fd = discrete(nxf, xdf, ied1, np.random.rand()) 520 elif region == "other": 521 fd = discrete(nxf, xdf, ied2, np.random.rand()) 522 try: 523 return fd 524 except: 525 return 0.0
Simulate hypocenter's down-dip relative position on the rupture surface based on Chiou & Youngs (2008)
Input arguments:
- eqType = type of earthquake (tectonic regime) "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
- region = geographic region where the earthquake occured "japan" "chile" "other"
Output Arguments:
- fd = down-dip relative position of hypocenter on rupture surface [0, 1] 0 ~ along the top-depth of the rupture 1 ~ along the bottom-depth of the rupture
528def get_hyp_along_strike_position(eqtype): 529 """ 530 Simulate hypocenter's along-strike relative position on the rupture surface based on Chiou & Youngs (2008) 531 532 Input arguments: 533 - eqType = type of earthquake (tectonic regime) 534 "crustal" = shallow-crustal in active tectonic regimes 535 "intraslab" = intraslab-type in subduction regimes 536 "interface" = interface-type in subduction regimes 537 "stable" = shallow-crustal in stable-continental regimes 538 539 Output Arguments: 540 - fl = along-strike relative position of hypocenter on rupture surface [0, 1] 541 0 ~ along the "left" edge of the rupture 542 1 ~ along the "right" edge of the rupture 543 """ 544 nxf = 11 545 xdf = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.00]) 546 hypx = np.array([0, 0.05, 0.125, 0.225, 0.35, 0.5, 0.65, 0.775, 0.875, 0.95, 1.00]) 547 hypxe = np.array( 548 [0.000, 0.007, 0.034, 0.112, 0.272, 0.500, 0.728, 0.888, 0.966, 0.993, 1.00] 549 ) 550 hypxa = np.array( 551 [0.00, 0.015, 0.057, 0.148, 0.301, 0.500, 0.699, 0.852, 0.943, 0.985, 1.00] 552 ) 553 if eqtype in ["crustal", "stable"]: 554 fl = discrete(nxf, xdf, hypx, np.random.rand()) 555 elif eqtype == "intraslab": 556 fl = discrete(nxf, xdf, hypxa, np.random.rand()) 557 elif eqtype == "interface": 558 fl = discrete(nxf, xdf, hypxe, np.random.rand()) 559 return fl
Simulate hypocenter's along-strike relative position on the rupture surface based on Chiou & Youngs (2008)
Input arguments:
- eqType = type of earthquake (tectonic regime) "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
Output Arguments:
- fl = along-strike relative position of hypocenter on rupture surface [0, 1] 0 ~ along the "left" edge of the rupture 1 ~ along the "right" edge of the rupture
567def LL2XY(zlon, zlat, lon, lat): 568 """ 569 Convert latitude/longitude to x/y coordinates on the basis of a spherical earth 570 (ignoring ellipsoidal effects) using the haversine formula. 571 572 Reference: http://www.movable-type.co.uk/scripts/latlong.html 573 574 Input Arguments: 575 - zlon = centered longitude (degrees): corresponding to x/y coordinate of (0,0) 576 - zlat = centered latitude (degrees): corresponding to x/y coordinate of (0,0) 577 - lon = numpy array of longitudes (degrees) 578 - lat = numpy array of latitudes (degrees) 579 580 Output Arguments: 581 -x = numpy array of x-coordinate 582 -y = numpy array of y-coordinates 583 584 """ 585 Rearth = 6371.0 586 dtr = np.arcsin(1.0) / 90 587 lam1 = (zlon + 360) * dtr if zlon < 0 else zlon * dtr 588 phi1 = zlat * dtr 589 590 # lam2 = (lon + 360)*dtr if lon < 0 else lon*dtr 591 if type(lon) == "float": 592 lam2 = (lon + 360) * dtr if lon < 0 else lon * dtr 593 else: 594 lam2 = np.where(lon < 0, (lon + 360) * dtr, lon * dtr) 595 596 phi2 = lat * dtr 597 dphi = phi2 - phi1 598 dlam = lam2 - lam1 599 a = np.sin(dphi / 2) ** 2 + np.cos(phi1) * np.cos(phi2) * np.sin(dlam / 2) ** 2 600 c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) 601 dist = Rearth * c 602 if dist == 0: 603 theta = 0 604 else: 605 theta = np.arctan2( 606 np.sin(dlam) * np.cos(phi2), 607 np.cos(phi1) * np.sin(phi2) - np.sin(phi1) * np.cos(phi2) * np.cos(dlam), 608 ) 609 x = dist * np.sin(theta) 610 y = dist * np.cos(theta) 611 612 return (x, y)
Convert latitude/longitude to x/y coordinates on the basis of a spherical earth (ignoring ellipsoidal effects) using the haversine formula.
Reference: http://www.movable-type.co.uk/scripts/latlong.html
Input Arguments:
- zlon = centered longitude (degrees): corresponding to x/y coordinate of (0,0)
- zlat = centered latitude (degrees): corresponding to x/y coordinate of (0,0)
- lon = numpy array of longitudes (degrees)
- lat = numpy array of latitudes (degrees)
Output Arguments: -x = numpy array of x-coordinate -y = numpy array of y-coordinates
615def XY2LL(zlon, zlat, x, y): 616 """ 617 Convert x/y coordinates to latitude/longitude on the basis of a spherical earth 618 (ignoring ellipsoidal effects) using the haversine formula. 619 620 Reference: http://www.movable-type.co.uk/scripts/latlong.html 621 622 Input Arguments: 623 - zlon = centered longitude (degrees): corresponding to x/y coordinate of (0,0) 624 - zlat = centered latitude (degrees): corresponding to x/y coordinate of (0,0) 625 - x = numpy array of x-coordinate 626 - y = numpy array of y-coordinates 627 628 Output Arguments: 629 - lon = numpy array of longitudes (degrees) 630 - lat = numpy array of latitudes (degrees) 631 632 """ 633 Rearth = 6371.0 634 dtr = np.arcsin(1.0) / 90 635 lam1 = (zlon + 360) * dtr if zlon < 0 else zlon * dtr 636 phi1 = zlat * dtr 637 638 d = np.sqrt(x**2 + y**2) 639 delta = d / Rearth 640 theta = np.arctan2(x, y) 641 phi2 = np.arcsin( 642 np.sin(phi1) * np.cos(delta) + np.cos(phi1) * np.sin(delta) * np.cos(theta) 643 ) 644 lam2 = lam1 + np.arctan2( 645 np.sin(theta) * np.sin(delta) * np.cos(phi1), 646 np.cos(delta) - np.sin(phi1) * np.sin(phi2), 647 ) 648 lat = phi2 / dtr 649 lon = lam2 / dtr 650 if type(lon) == "float": 651 lon = lon - 360 if lon > 180 else lon 652 else: 653 lon = np.where(lon > 180, lon - 360, lon) 654 655 return (lon, lat)
Convert x/y coordinates to latitude/longitude on the basis of a spherical earth (ignoring ellipsoidal effects) using the haversine formula.
Reference: http://www.movable-type.co.uk/scripts/latlong.html
Input Arguments:
- zlon = centered longitude (degrees): corresponding to x/y coordinate of (0,0)
- zlat = centered latitude (degrees): corresponding to x/y coordinate of (0,0)
- x = numpy array of x-coordinate
- y = numpy array of y-coordinates
Output Arguments:
- lon = numpy array of longitudes (degrees)
- lat = numpy array of latitudes (degrees)
658def pointTriangleDistance(TRI_xyz, P_xyz): 659 """ 660 Compute shortest 3D distance between a triangle and point (Eberly 1999) 661 662 Reference: https://www.geometrictools.com/Documentation/DistancePoint3Triangle3.pdf 663 664 Coded in Python by Meera Kota (UCLA) 665 666 Input Arguments: 667 - TRI_xyz = numpy arrays of 3D triangles in XY space [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3]] 668 - P_xyz = numpy array of 3D points in XY space [[x1,y1,z1],...,[xn,yn,zn]] where n = number of points 669 """ 670 E0 = TRI_xyz[:, 1] - TRI_xyz[:, 0] 671 E1 = TRI_xyz[:, 2] - TRI_xyz[:, 0] 672 a = np.sum(np.multiply(E0, E0), axis=1) 673 b = np.sum(np.multiply(E0, E1), axis=1) 674 c = np.sum(np.multiply(E1, E1), axis=1) 675 676 det = a * c - b * b 677 det[det == 0] = 1.0e-8 678 679 D = TRI_xyz[:, 0] - P_xyz 680 681 d = np.sum(np.multiply(E0, D), axis=2) 682 e = np.sum(np.multiply(E1, D), axis=2) 683 f = np.sum(np.multiply(D, D), axis=2) 684 685 s = b * e - c * d 686 t = b * d - a * e 687 688 sqrdistance = np.empty((len(P_xyz), len(TRI_xyz)), dtype=float) 689 690 # Region 4 691 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d < 0.0) & (-d >= a) 692 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 693 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d < 0.0) & (-d < a) 694 sqrdistance[cond] = (-d * d / a + f)[cond] 695 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d >= 0.0) & (e >= 0.0) 696 sqrdistance[cond] = (f)[cond] 697 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d >= 0.0) & (e < 0.0) & (-e >= c) 698 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 699 cond = (s + t <= det) & (s < 0.0) & (t < 0.0) & (d >= 0.0) & (e < 0.0) & (-e < c) 700 sqrdistance[cond] = (-e * e / c + f)[cond] 701 702 # Region 3 703 cond = (s + t <= det) & (s < 0.0) & (t >= 0.0) & (e >= 0.0) 704 sqrdistance[cond] = (f)[cond] 705 cond = (s + t <= det) & (s < 0.0) & (t >= 0.0) & (e < 0.0) & (-e >= c) 706 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 707 cond = (s + t <= det) & (s < 0.0) & (t >= 0.0) & (e < 0.0) & (-e < c) 708 sqrdistance[cond] = (-e * e / c + f)[cond] 709 710 # Region 5 711 cond = (s + t <= det) & (s >= 0.0) & (t < 0.0) & (d >= 0.0) 712 sqrdistance[cond] = (f)[cond] 713 cond = (s + t <= det) & (s >= 0.0) & (t < 0.0) & (d < 0.0) & (-d >= a) 714 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 715 cond = (s + t <= det) & (s >= 0.0) & (t < 0.0) & (d < 0.0) & (-d < a) 716 sqrdistance[cond] = (-d * d / a + f)[cond] 717 718 # Region 0 719 invDet = 1.0 / det 720 stemp = s * invDet 721 ttemp = t * invDet 722 cond = (s + t <= det) & (s >= 0) & (t >= 0) 723 sqrdistance[cond] = ( 724 stemp * (a * stemp + b * ttemp + 2.0 * d) 725 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 726 + f 727 )[cond] 728 729 # Region 2 730 tmp0 = b + d 731 tmp1 = c + e 732 numer = tmp1 - tmp0 733 denom = a - 2.0 * b + c 734 denom[denom == 0] = 1.0e-8 735 stemp = numer / denom 736 ttemp = 1.0 - stemp 737 cond = (s + t > det) & (s < 0.0) & (tmp1 > tmp0) & (numer >= denom) 738 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 739 cond = (s + t > det) & (s < 0.0) & (tmp1 > tmp0) & (numer < denom) 740 sqrdistance[cond] = ( 741 stemp * (a * stemp + b * ttemp + 2.0 * d) 742 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 743 + f 744 )[cond] 745 cond = (s + t > det) & (s < 0.0) & (tmp1 <= tmp0) & (tmp1 <= 0.0) 746 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 747 cond = (s + t > det) & (s < 0.0) & (tmp1 <= tmp0) & (tmp1 > 0.0) & (e >= 0.0) 748 sqrdistance[cond] = (f)[cond] 749 cond = (s + t > det) & (s < 0.0) & (tmp1 <= tmp0) & (tmp1 > 0.0) & (e < 0.0) 750 sqrdistance[cond] = (-e * e / c + f)[cond] 751 752 # Region 6 753 tmp0 = b + e 754 tmp1 = a + d 755 numer = tmp1 - tmp0 756 denom = a - 2.0 * b + c 757 denom[denom == 0] = 1.0e-8 758 ttemp = numer / denom 759 stemp = 1.0 - ttemp 760 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 > tmp0) & (numer >= denom) 761 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 762 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 > tmp0) & (numer < denom) 763 sqrdistance[cond] = ( 764 stemp * (a * stemp + b * ttemp + 2.0 * d) 765 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 766 + f 767 )[cond] 768 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 <= tmp0) & (tmp1 <= 0) 769 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 770 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 <= tmp0) & (tmp1 > 0) & (d >= 0) 771 sqrdistance[cond] = (f)[cond] 772 cond = (s + t > det) & (s >= 0) & (t < 0) & (tmp1 <= tmp0) & (tmp1 > 0) & (d < 0) 773 sqrdistance[cond] = (-d * d / a + f)[cond] 774 775 # Region 1 776 numer = c + e - b - d 777 denom = a - 2.0 * b + c 778 stemp = numer / denom 779 ttemp = 1.0 - stemp 780 cond = (s + t > det) & (s >= 0) & (t >= 0) & (numer <= 0) 781 sqrdistance[cond] = (c + 2.0 * e + f)[cond] 782 cond = (s + t > det) & (s >= 0) & (t >= 0) & (numer > 0) & (numer >= denom) 783 sqrdistance[cond] = (a + 2.0 * d + f)[cond] 784 cond = (s + t > det) & (s >= 0) & (t >= 0) & (numer > 0) & (numer < denom) 785 sqrdistance[cond] = ( 786 stemp * (a * stemp + b * ttemp + 2.0 * d) 787 + ttemp * (b * stemp + c * ttemp + 2.0 * e) 788 + f 789 )[cond] 790 791 # account for numerical round-off error 792 sqrdistance[sqrdistance <= 0] = 0 793 return np.sqrt(sqrdistance).T
Compute shortest 3D distance between a triangle and point (Eberly 1999)
Reference: https://www.geometrictools.com/Documentation/DistancePoint3Triangle3.pdf
Coded in Python by Meera Kota (UCLA)
Input Arguments:
- TRI_xyz = numpy arrays of 3D triangles in XY space [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3]]
- P_xyz = numpy array of 3D points in XY space [[x1,y1,z1],...,[xn,yn,zn]] where n = number of points
796def check_input_arguments( 797 eqType, method, nsims, strike, dip, rake, strike2, dip2, rake2 798): 799 # Check that required arguments are defined for each simulation method ------------------------------------------------- 800 if method in ["A", "D"]: 801 if None in np.array([strike, dip, rake]): 802 raise ValueError( 803 "'strike', 'dip', and 'rake' must be defined when using 'method' = '%s'" 804 % method 805 ) 806 if method == "B": 807 if None in np.array([strike2, dip2, rake2]): 808 raise ValueError( 809 "'strike2', 'dip2', and 'rake2' must be defined when using 'method' = 'B'" 810 ) 811 if method == "C": 812 if None in np.array([strike, dip, rake, strike2, dip2, rake2]): 813 raise ValueError( 814 "'strike', 'dip', 'rake', 'strike2', 'dip2', and 'rake2' must be defined when using 'method' = 'C'" 815 ) 816 817 # Check that number of simulations are compatible with sepcified eqType ------------------------------------------------ 818 if (eqType == "crustal") & (nsims[6] > 0): 819 raise ValueError( 820 "nsims[6] = %i: ContrerasEtAl2022 cannot be used for 'crustal' events" 821 % nsims[6] 822 ) 823 elif eqType == "stable": 824 if nsims[0] > 0: 825 raise ValueError( 826 "nsims[0] = %i: WellsCoppersmith1994 cannot be used for 'stable' events" 827 % nsims[0] 828 ) 829 elif nsims[2] > 0: 830 raise ValueError( 831 "nsims[2] = %i: ThingbaijamEtAl2017 cannot be used for 'stable' events" 832 % nsims[2] 833 ) 834 elif sum(nsims[3:6]) > 0: 835 raise ValueError( 836 "nsims[3:6] = [%i,%i,%i]: ChiouYoungs2008 cannot be used for 'stable' events" 837 % (nsims[3], nsims[4], nsims[5]) 838 ) 839 elif nsims[6] > 0: 840 raise ValueError( 841 "nsims[6] = %i: ContrerasEtAl2022 cannot be used for 'stable' events" 842 % nsims[6] 843 ) 844 elif eqType in ["interface", "intraslab"]: 845 if nsims[0] > 0: 846 raise ValueError( 847 "nsims[0] = %i: WellsCoppersmith1994 cannot be used for '%s' events" 848 % (nsims[0], eqType) 849 ) 850 elif nsims[1] > 0: 851 raise ValueError( 852 "nsims[1] = %i: Leonard2014 cannot be used for '%s' events" 853 % (nsims[1], eqType) 854 ) 855 elif sum(nsims[3:6]) > 0: 856 raise ValueError( 857 "nsims[3:6] = [%i,%i,%i]: ChiouYoungs2008 cannot be used for '%s' events" 858 % (nsims[3], nsims[4], nsims[5], eqType) 859 ) 860 861 # Check that there are only positive integers for the number of simulations -------------------------------------------- 862 if np.any(np.array(nsims), where=np.array(nsims) < 0): 863 raise ValueError( 864 "nsims = %s: can not specify a negative number of simulatiuons for any scaling relationship" 865 % str(list(nsims)) 866 ) 867 868 # Check that the total number of simulations is odd and greater than zero ---------------------------------------------- 869 if sum(nsims) % 2 == 0: 870 if eqType == "crustal": 871 if nsims[0] > 0: 872 nsims[0] = nsims[0] + 1 873 elif nsims[1] > 0: 874 nsims[1] = nsims[1] + 1 875 elif nsims[2] > 0: 876 nsims[2] = nsims[2] + 1 877 elif nsims[3] > 0: 878 nsims[3] = nsims[3] + 1 879 elif nsims[4] > 0: 880 nsims[4] = nsims[4] + 1 881 elif nsims[5] > 0: 882 nsims[5] = nsims[5] + 1 883 else: 884 raise ValueError( 885 "nsims = %s: must specify the number of simulations partitioned to each scaling relationship" 886 % str(list(nsims)) 887 ) 888 if eqType == "stable": 889 if nsims[1] > 0: 890 nsims[1] = nsims[1] + 1 891 else: 892 raise ValueError( 893 "nsims = %s: must specify the number of simulations partitioned to each scaling relationship" 894 % str(list(nsims)) 895 ) 896 if eqType in ["interface", "intraslab"]: 897 if nsims[6] > 0: 898 nsims[6] = nsims[6] + 1 899 elif nsims[2] > 0: 900 nsims[2] = nsims[2] + 1 901 else: 902 raise ValueError( 903 "nsims = %s: must specify the number of simulations partitioned to each scaling relationship" 904 % str(list(nsims)) 905 )
925def get_rupture_surface_simulations( 926 eqid, 927 eqType, 928 region, 929 elat, 930 elon, 931 hypd, 932 magnitude, 933 method, 934 nsims, 935 mechanism=None, 936 strike=None, 937 dip=None, 938 rake=None, 939 strike2=None, 940 dip2=None, 941 rake2=None, 942): 943 """ 944 Simulate earthquake rupture surface that minimizes the difference between the median distance of a pseudo-grid of sites 945 and a stochastic set of possible ruptures. Randomized fault attributes are dependent on the fault category. Fault scaling 946 models depend on earthquake type. Adapted from the CCLD routine originally coded in Frortran by Chiou and Youngs (2008). 947 948 Input Arguments: 949 - eqid = unique integer identifier for the event (to set the random seed) 950 - eqType = type of earthquake: 951 "crustal" = shallow-crustal in active tectonic regimes 952 "intraslab" = intraslab-type in subduction regimes 953 "interface" = interface-type in subduction regimes 954 "stable" = shallow-crustal in stable-continental regimes 955 - region = geographic region where the earthquake occured" 956 "japan" 957 "chile" 958 "other" 959 - elat = hypocenter latitude (degrees) 960 - elon = hypocenter longiude (degrees) 961 - hypd = hypocenter depth (km); positive into the ground 962 - magnitude = earthquake moment magnitude (Mw) 963 - method = code for rupture simulation constraints 964 "A" = strike, dip, and rake for first nodal plane solution preferred 965 (optional "strike", "dip", and "rake" arguments are required) 966 Warning: not recommended 967 "B" = strike, dip, and rake for second nodal plane solution preferred 968 (optional "strike2", "dip2", and "rake2" arguments are required) 969 Warning: not recommended 970 "C" = strike, dip, and rake are known for two nodal planes, and neither is preferred 971 (optional "strike", "dip", "rake", "strike2", "dip2", and "rake2" arguments are required) 972 "D" = One nodal plane solution for strike, dip, and rake; randomize the strike and dip 973 (optional "strike", "dip", and "rake" arguments are required) 974 Warning: not recommended 975 "E" = No nodal plane solutions; randomize strike, dip, and rake 976 (dip and rake are assigned based on faulting mechanism) 977 (if optional "mechanism" argument is not speficied, simulations randomly assign one) 978 - nsims = Number of simulations assigned to each M-scaling relationship. Total number of simulations should be odd. 979 nsims[0] = Wells & Coppersmith (1994) - [recommended 334] 980 nsims[1] = Leonard (2014) - [recommended 333] 981 nsims[2] = Thingbaijam et al. (2017) [recommended 333] 982 nsims[3] = Chiou & Youngs (2008) aspect ratio model with Wells & Coppersmith (1994) area relationship [recommended 111] 983 nsims[4] = Chiou & Youngs (2008) aspect ratio model with Leonard (2014) area relationship [recommended 111] 984 nsims[5] = Chiou & Youngs (2008) aspect ratio model with Thingbaijam et al. (2017) area relationship [recommended 111] 985 nsims[6] = Contreras et al. (2022) - [recommended 333] 986 - mechanim = known or preferred style-of-faulting [default None] 987 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 988 "NM" = normal: -150 < rake < -30 989 "RV" = reverse: 30 < rake < 150 990 - strike = strike-angle (degrees) of the first nodal plane solution [default None] 991 - dip = dip-angle (degrees) of the first nodal plane solution [default None] 992 - rake = rake-angle (degrees) of the first nodal plane solution [default None] 993 - strike2 = strike-angle (degrees) of the second nodal plane solution [default None] 994 - dip2 = dip-angle (degrees) of the second nodal plane solution [default None] 995 - rake2 = rake-angle (degrees) of the second nodal plane solution [default None] 996 997 Output Arguments: 998 - median_simulation = index of selected (median) rupture 999 - along_strikes = numpy array of the hypocenter along-strike relative position [0, 1] 1000 - down_dips = numpy array of the hypocenter down-dip relative position [0, 1] 1001 - lon1s = numpy array of longitude (degrees) for the ULC rupture vertex 1002 - lat1s = numpy array of latitude (degrees) for the ULC rupture vertex 1003 - rpz1s = numpy array of depth (km) for the ULC rupture vertex 1004 - lon2s = numpy array of longitude (degrees) for the URC rupture vertex 1005 - lat2s = numpy array of latitude (degrees) for the URC rupture vertex 1006 - rpz2s = numpy array of depth (km) for the URC rupture vertex 1007 - lon3s = numpy array of longitude (degrees) for the LLC rupture vertex 1008 - lat3s = numpy array of latitude (degrees) for the LLC rupture vertex 1009 - rpz3s = numpy array of depth (km) for the LLC rupture vertex 1010 - lon4s = numpy array of longitude (degrees) for the LRC rupture vertex 1011 - lat4s = numpy array of latitude (degrees) for the LRC rupture vertex 1012 - rpz4s = numpy array of depth (km) for the LRC rupture vertex 1013 - strikes = numpy array of strike angle (degrees) 1014 - dips = numpy array of dip angle (degrees) 1015 - rakes = numpy array of rake angle (degrees) 1016 - models = numpy array of magnitude-scaling relationship used for each realization 1017 - areas = numpy array of area (km^2) 1018 - aspect_ratios = numpy array of aspect ratio 1019 - lengths = numpy array of rupture length (km) 1020 - widths = numpy array of rupture width (km) 1021 - top_depths = numpy array of top depth (km) of rupture 1022 - bottom_depths = numpy array of bottom depth (km) of rupture 1023 """ 1024 1025 check_input_arguments( 1026 eqType, method, nsims, strike, dip, rake, strike2, dip2, rake2 1027 ) 1028 try: 1029 strike = float(strike) 1030 dip = float(dip) 1031 rake = float(rake) 1032 except: 1033 pass 1034 try: 1035 strike2 = float(strike2) 1036 dip2 = float(dip2) 1037 rake2 = float(rake2) 1038 except: 1039 pass 1040 1041 # Set random seed ------------------------------------------------------------------------------------------------------- 1042 np.random.seed(seed=eqid) 1043 total_sims = np.sum(nsims) 1044 1045 # Set earthquake parameters -------------------------------------------------------------------------------------------- 1046 if method == "A": 1047 # Prefer the first nodal plane solution: 1048 strikes = np.repeat([strike], total_sims) 1049 dips = np.repeat([dip], total_sims) 1050 rakes = np.repeat([rake], total_sims) 1051 mechanisms = np.repeat(get_mechanism_based_on_rake(rake), total_sims) 1052 elif method == "B": 1053 # Prefer the second nodal plane solution: 1054 strikes = np.repeat([strike2], total_sims) 1055 dips = np.repeat([dip2], total_sims) 1056 rakes = np.repeat([rake2], total_sims) 1057 mechanisms = np.repeat(get_mechanism_based_on_rake(rake2), total_sims) 1058 elif method == "C": 1059 # Randomize between the two nodal plane solutions: 1060 nodal_planes = np.random.choice([1, 2], total_sims) 1061 strikes = np.where(nodal_planes == 1, strike, strike2) 1062 dips = np.where(nodal_planes == 1, dip, dip2) 1063 rakes = np.where(nodal_planes == 1, rake, rake2) 1064 mechanisms = v_get_mechanism_based_on_rake(rakes) 1065 elif method == "D": 1066 # Randomize strike and dip with a uniform distributions centered around the perscribed values 1067 strikes = np.random.uniform( 1068 strike - 30.0, strike + 30.0, total_sims 1069 ) # +/- 30 degrees 1070 strikes = np.where(strikes < 0, strikes + 360, strikes) # strike >= 0 1071 strikes = np.where(strikes >= 360.0, strikes - 360.0, strikes) # strike < 360 1072 dips = np.random.uniform(dip - 10.0, dip + 10.0, total_sims) # +/- 10 degrees 1073 dips = np.where(dips < 10.0, 10.0, dips) # 10 <= dip 1074 dips = np.where( 1075 dips > 89.9999999, 89.9999999, dips 1076 ) # dip <= 90 # cannot divide by zero 1077 rakes = np.repeat([rake], total_sims) 1078 mechanisms = v_get_mechanism_based_on_rake(rakes) 1079 elif method == "E": 1080 # Randomize everything based on prescribed mechanism 1081 # if mechanism is not prescribed, then randomize everything 1082 if mechanism == None: 1083 mechanisms = np.random.choice(["SS", "NM", "RV"], total_sims) 1084 else: 1085 mechanisms = np.repeat(mechanism, total_sims) 1086 strikes = np.random.uniform(0.0, 360.0, total_sims) 1087 strikes = np.where( 1088 strikes >= 360.0, strikes - 360.0, strikes 1089 ) # 0 <= strike < 360 1090 rakes = np.where(mechanisms == "SS", 0.0, mechanisms) 1091 rakes = np.where(rakes == "NM", -90.0, rakes) 1092 rakes = np.where(rakes == "RV", 90.0, rakes) 1093 dips = np.where( 1094 mechanisms == "SS", 89.9999999, mechanisms 1095 ) # cannot divide by zero 1096 dips = np.where(dips == "NM", 55.0, dips) 1097 dips = np.where(dips == "RV", 40.0, dips) 1098 dips = dips.astype(float) 1099 dips = np.where( 1100 dips > 89.9999999, 89.9999999, dips 1101 ) # dip <= 90 # cannot divide by zero 1102 1103 # Convert degrees to radians ------------------------------------------------------------------------------------------- 1104 strikes *= np.pi / 180.0 1105 dips *= np.pi / 180.0 1106 1107 # Simulate hypocenter down-dip and along-strike position --------------------------------------------------------------- 1108 down_dips = v_get_hyp_down_dip_position(np.repeat(eqType, total_sims), region) 1109 along_strikes = v_get_hyp_along_strike_position(np.repeat(eqType, total_sims)) 1110 1111 # Simulate rupture geometry using magnitude-scaling relationships ------------------------------------------------------ 1112 if nsims[0] > 0: 1113 nsim_WellsCoppersmith1994 = nsims[0] 1114 relationships_WellsCoppersmith1994 = v_WellsCoppersmith1994( 1115 np.repeat(magnitude, nsim_WellsCoppersmith1994), 1116 np.repeat(eqType, nsim_WellsCoppersmith1994), 1117 ) 1118 areas_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[0] 1119 aspect_ratios_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[1] 1120 lengths_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[2] 1121 widths_WellsCoppersmith1994 = relationships_WellsCoppersmith1994[3] 1122 models = np.repeat("WellsCoppersmith1994", nsim_WellsCoppersmith1994) 1123 else: 1124 areas_WellsCoppersmith1994, aspect_ratios_WellsCoppersmith1994 = np.array( 1125 [] 1126 ), np.array([]) 1127 lengths_WellsCoppersmith1994, widths_WellsCoppersmith1994 = np.array( 1128 [] 1129 ), np.array([]) 1130 models = np.array([]) 1131 1132 if nsims[1] > 0: 1133 nsim_Leonard2014 = nsims[1] 1134 mechanisms_Leonard2014 = mechanisms[np.sum(nsims[:1]) : np.sum(nsims[:2])] 1135 relationships_Leonard2014 = v_Leonard2014( 1136 np.repeat(magnitude, nsim_Leonard2014), 1137 mechanisms_Leonard2014, 1138 np.repeat(eqType, nsim_Leonard2014), 1139 ) 1140 areas_Leonard2014 = relationships_Leonard2014[0] 1141 aspect_ratios_Leonard2014 = relationships_Leonard2014[1] 1142 lengths_Leonard2014 = relationships_Leonard2014[2] 1143 widths_Leonard2014 = relationships_Leonard2014[3] 1144 models = np.concatenate((models, np.repeat("Leonard2014", nsim_Leonard2014))) 1145 else: 1146 areas_Leonard2014, aspect_ratios_Leonard2014 = np.array([]), np.array([]) 1147 lengths_Leonard2014, widths_Leonard2014 = np.array([]), np.array([]) 1148 1149 if nsims[2] > 0: 1150 nsim_ThingbaijamEtAl2017 = nsims[2] 1151 mechanisms_ThingbaijamEtAl2017 = mechanisms[ 1152 np.sum(nsims[:2]) : np.sum(nsims[:3]) 1153 ] 1154 relationships_ThingbaijamEtAl2017 = v_ThingbaijamEtAl2017( 1155 np.repeat(magnitude, nsim_ThingbaijamEtAl2017), 1156 mechanisms_ThingbaijamEtAl2017, 1157 np.repeat(eqType, nsim_ThingbaijamEtAl2017), 1158 ) 1159 areas_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[0] 1160 aspect_ratios_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[1] 1161 lengths_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[2] 1162 widths_ThingbaijamEtAl2017 = relationships_ThingbaijamEtAl2017[3] 1163 models = np.concatenate( 1164 (models, np.repeat("ThingbaijamEtAl2017", nsim_ThingbaijamEtAl2017)) 1165 ) 1166 else: 1167 areas_ThingbaijamEtAl2017, aspect_ratios_ThingbaijamEtAl2017 = np.array( 1168 [] 1169 ), np.array([]) 1170 lengths_ThingbaijamEtAl2017, widths_ThingbaijamEtAl2017 = np.array( 1171 [] 1172 ), np.array([]) 1173 1174 if nsims[3] > 0: 1175 nsim_ChiouYoungs2008_WellsCoppersmith1994 = nsims[3] 1176 mechanisms_ChiouYoungs2008_WellsCoppersmith1994 = mechanisms[ 1177 np.sum(nsims[:3]) : np.sum(nsims[:4]) 1178 ] 1179 relationships_ChiouYoungs2008_WellsCoppersmith1994 = v_ChiouYoungs2008( 1180 np.repeat(magnitude, nsim_ChiouYoungs2008_WellsCoppersmith1994), 1181 mechanisms_ChiouYoungs2008_WellsCoppersmith1994, 1182 np.repeat(eqType, nsim_ChiouYoungs2008_WellsCoppersmith1994), 1183 "WellsCoppersmith1994", 1184 ) 1185 areas_ChiouYoungs2008_WellsCoppersmith1994 = ( 1186 relationships_ChiouYoungs2008_WellsCoppersmith1994[0] 1187 ) 1188 aspect_ratios_ChiouYoungs2008_WellsCoppersmith1994 = ( 1189 relationships_ChiouYoungs2008_WellsCoppersmith1994[1] 1190 ) 1191 lengths_ChiouYoungs2008_WellsCoppersmith1994 = ( 1192 relationships_ChiouYoungs2008_WellsCoppersmith1994[2] 1193 ) 1194 widths_ChiouYoungs2008_WellsCoppersmith1994 = ( 1195 relationships_ChiouYoungs2008_WellsCoppersmith1994[3] 1196 ) 1197 models = np.concatenate( 1198 ( 1199 models, 1200 np.repeat( 1201 "ChiouYoungs2008_WellsCoppersmith1994", 1202 nsim_ChiouYoungs2008_WellsCoppersmith1994, 1203 ), 1204 ) 1205 ) 1206 else: 1207 ( 1208 areas_ChiouYoungs2008_WellsCoppersmith1994, 1209 aspect_ratios_ChiouYoungs2008_WellsCoppersmith1994, 1210 ) = np.array([]), np.array([]) 1211 ( 1212 lengths_ChiouYoungs2008_WellsCoppersmith1994, 1213 widths_ChiouYoungs2008_WellsCoppersmith1994, 1214 ) = np.array([]), np.array([]) 1215 1216 if nsims[4] > 0: 1217 nsim_ChiouYoungs2008_Leonard2014 = nsims[4] 1218 mechanisms_ChiouYoungs2008_Leonard2014 = mechanisms[ 1219 np.sum(nsims[:4]) : np.sum(nsims[:5]) 1220 ] 1221 relationships_ChiouYoungs2008_Leonard2014 = v_ChiouYoungs2008( 1222 np.repeat(magnitude, nsim_ChiouYoungs2008_Leonard2014), 1223 mechanisms_ChiouYoungs2008_Leonard2014, 1224 np.repeat(eqType, nsim_ChiouYoungs2008_Leonard2014), 1225 "Leonard2014", 1226 ) 1227 areas_ChiouYoungs2008_Leonard2014 = relationships_ChiouYoungs2008_Leonard2014[0] 1228 aspect_ratios_ChiouYoungs2008_Leonard2014 = ( 1229 relationships_ChiouYoungs2008_Leonard2014[1] 1230 ) 1231 lengths_ChiouYoungs2008_Leonard2014 = relationships_ChiouYoungs2008_Leonard2014[ 1232 2 1233 ] 1234 widths_ChiouYoungs2008_Leonard2014 = relationships_ChiouYoungs2008_Leonard2014[ 1235 3 1236 ] 1237 models = np.concatenate( 1238 ( 1239 models, 1240 np.repeat( 1241 "ChiouYoungs2008_Leonard2014", nsim_ChiouYoungs2008_Leonard2014 1242 ), 1243 ) 1244 ) 1245 else: 1246 areas_ChiouYoungs2008_Leonard2014, aspect_ratios_ChiouYoungs2008_Leonard2014 = ( 1247 np.array([]), 1248 np.array([]), 1249 ) 1250 lengths_ChiouYoungs2008_Leonard2014, widths_ChiouYoungs2008_Leonard2014 = ( 1251 np.array([]), 1252 np.array([]), 1253 ) 1254 1255 if nsims[5] > 0: 1256 nsim_ChiouYoungs2008_ThingbaijamEtAl2017 = nsims[5] 1257 mechanisms_ChiouYoungs2008_ThingbaijamEtAl2017 = mechanisms[ 1258 np.sum(nsims[:5]) : np.sum(nsims[:6]) 1259 ] 1260 relationships_ChiouYoungs2008_ThingbaijamEtAl2017 = v_ChiouYoungs2008( 1261 np.repeat(magnitude, nsim_ChiouYoungs2008_ThingbaijamEtAl2017), 1262 mechanisms_ChiouYoungs2008_ThingbaijamEtAl2017, 1263 np.repeat(eqType, nsim_ChiouYoungs2008_ThingbaijamEtAl2017), 1264 "ThingbaijamEtAl2017", 1265 ) 1266 areas_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1267 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[0] 1268 ) 1269 aspect_ratios_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1270 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[1] 1271 ) 1272 lengths_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1273 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[2] 1274 ) 1275 widths_ChiouYoungs2008_ThingbaijamEtAl2017 = ( 1276 relationships_ChiouYoungs2008_ThingbaijamEtAl2017[3] 1277 ) 1278 models = np.concatenate( 1279 ( 1280 models, 1281 np.repeat( 1282 "ChiouYoungs2008_ThingbaijamEtAl2017", 1283 nsim_ChiouYoungs2008_ThingbaijamEtAl2017, 1284 ), 1285 ) 1286 ) 1287 else: 1288 ( 1289 areas_ChiouYoungs2008_ThingbaijamEtAl2017, 1290 aspect_ratios_ChiouYoungs2008_ThingbaijamEtAl2017, 1291 ) = np.array([]), np.array([]) 1292 ( 1293 lengths_ChiouYoungs2008_ThingbaijamEtAl2017, 1294 widths_ChiouYoungs2008_ThingbaijamEtAl2017, 1295 ) = np.array([]), np.array([]) 1296 1297 if nsims[6] > 0: 1298 nsim_ContrerasEtAl2022 = nsims[6] 1299 mechanisms_ContrerasEtAl2022 = mechanisms[np.sum(nsims[:6]) : np.sum(nsims[:7])] 1300 relationships_ContrerasEtAl2022 = v_ContrerasEtAl2022( 1301 np.repeat(magnitude, nsim_ContrerasEtAl2022), 1302 np.repeat(eqType, nsim_ContrerasEtAl2022), 1303 ) 1304 areas_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[0] 1305 aspect_ratios_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[1] 1306 lengths_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[2] 1307 widths_ContrerasEtAl2022 = relationships_ContrerasEtAl2022[3] 1308 models = np.concatenate( 1309 (models, np.repeat("ContrerasEtAl2022", nsim_ContrerasEtAl2022)) 1310 ) 1311 else: 1312 areas_ContrerasEtAl2022, aspect_ratios_ContrerasEtAl2022 = np.array( 1313 [] 1314 ), np.array([]) 1315 lengths_ContrerasEtAl2022, widths_ContrerasEtAl2022 = np.array([]), np.array([]) 1316 1317 areas = np.concatenate( 1318 ( 1319 areas_WellsCoppersmith1994, 1320 areas_Leonard2014, 1321 areas_ThingbaijamEtAl2017, 1322 areas_ChiouYoungs2008_WellsCoppersmith1994, 1323 areas_ChiouYoungs2008_Leonard2014, 1324 areas_ChiouYoungs2008_ThingbaijamEtAl2017, 1325 areas_ContrerasEtAl2022, 1326 ) 1327 ) 1328 aspect_ratios = np.concatenate( 1329 ( 1330 aspect_ratios_WellsCoppersmith1994, 1331 aspect_ratios_Leonard2014, 1332 aspect_ratios_ThingbaijamEtAl2017, 1333 aspect_ratios_ChiouYoungs2008_WellsCoppersmith1994, 1334 aspect_ratios_ChiouYoungs2008_Leonard2014, 1335 aspect_ratios_ChiouYoungs2008_ThingbaijamEtAl2017, 1336 aspect_ratios_ContrerasEtAl2022, 1337 ) 1338 ) 1339 lengths = np.concatenate( 1340 ( 1341 lengths_WellsCoppersmith1994, 1342 lengths_Leonard2014, 1343 lengths_ThingbaijamEtAl2017, 1344 lengths_ChiouYoungs2008_WellsCoppersmith1994, 1345 lengths_ChiouYoungs2008_Leonard2014, 1346 lengths_ChiouYoungs2008_ThingbaijamEtAl2017, 1347 lengths_ContrerasEtAl2022, 1348 ) 1349 ) 1350 widths = np.concatenate( 1351 ( 1352 widths_WellsCoppersmith1994, 1353 widths_Leonard2014, 1354 widths_ThingbaijamEtAl2017, 1355 widths_ChiouYoungs2008_WellsCoppersmith1994, 1356 widths_ChiouYoungs2008_Leonard2014, 1357 widths_ChiouYoungs2008_ThingbaijamEtAl2017, 1358 widths_ContrerasEtAl2022, 1359 ) 1360 ) 1361 1362 # Compute geometric parameters ----------------------------------------------------------------------------------------- 1363 xf = np.sin(strikes) * lengths * along_strikes 1364 yf = np.cos(strikes) * lengths * along_strikes 1365 xb = np.sin(strikes + np.pi) * lengths * (1.0 - along_strikes) 1366 yb = np.cos(strikes + np.pi) * lengths * (1.0 - along_strikes) 1367 rwh = widths * np.cos(dips) 1368 rwv = widths * np.sin(dips) 1369 top_depths = hypd - rwv * down_dips 1370 down_dips = np.where( 1371 top_depths < 0.0, hypd / rwv, down_dips 1372 ) # make sure hypocenter is in the ground 1373 top_depths = np.where(top_depths < 0.0, 0.0, top_depths) # top_depth > 0.0 1374 bottom_depths = top_depths + rwv 1375 1376 # Compute top points of the rupture surface 1 = ULC & 2 = URC ---------------------------------------------------------- 1377 rpx1s = xf + np.sin(strikes - np.arcsin(1.0)) * rwh * down_dips 1378 rpy1s = yf + np.cos(strikes - np.arcsin(1.0)) * rwh * down_dips 1379 rpz1s = top_depths 1380 1381 rpx2s = xb + np.sin(strikes - np.arcsin(1.0)) * rwh * down_dips 1382 rpy2s = yb + np.cos(strikes - np.arcsin(1.0)) * rwh * down_dips 1383 rpz2s = top_depths 1384 1385 # Compute bottom points of the rupture surface 3 = LRC & 4 = LLC ------------------------------------------------------- 1386 rpx3s = xf + np.sin(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1387 rpy3s = yf + np.cos(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1388 rpz3s = bottom_depths 1389 1390 rpx4s = xb + np.sin(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1391 rpy4s = yb + np.cos(strikes + np.arcsin(1.0)) * rwh * (1.0 - down_dips) 1392 rpz4s = bottom_depths 1393 1394 # Create a psudo-grid of stations -------------------------------------------------------------------------------------- 1395 r = np.array( 1396 list(np.arange(2, 20, 2)) 1397 + list(np.arange(25, 55, 5)) 1398 + list(np.arange(60, 110, 10)) 1399 + list(np.arange(125, 325, 25)) 1400 ) 1401 theta = np.pi * np.linspace(0, 360, 25)[:-1] / 180 1402 psx = np.multiply( 1403 np.repeat(r.reshape(1, len(r)), len(theta), axis=0), 1404 np.sin(theta.reshape(len(theta), 1)), 1405 ).flatten() 1406 psy = np.multiply( 1407 np.repeat(r.reshape(1, len(r)), len(theta), axis=0), 1408 np.cos(theta.reshape(len(theta), 1)), 1409 ).flatten() 1410 1411 # Compute distances to each psudo-station ------------------------------------------------------------------------------ 1412 SITES = np.array([[[x, y, 0]] for x, y in zip(psx, psy)]) 1413 TRI_123 = np.array( 1414 [ 1415 [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] 1416 for x1, x2, x3, y1, y2, y3, z1, z2, z3 in zip( 1417 rpx1s, rpx2s, rpx3s, rpy1s, rpy2s, rpy3s, rpz1s, rpz2s, rpz3s 1418 ) 1419 ] 1420 ) 1421 TRI_234 = np.array( 1422 [ 1423 [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] 1424 for x1, x2, x3, y1, y2, y3, z1, z2, z3 in zip( 1425 rpx2s, rpx4s, rpx3s, rpy2s, rpy4s, rpy3s, rpz2s, rpz4s, rpz3s 1426 ) 1427 ] 1428 ) 1429 rrups1 = pointTriangleDistance(TRI_123, SITES) 1430 rrups2 = pointTriangleDistance(TRI_234, SITES) 1431 rrups = np.minimum(rrups1, rrups2) 1432 1433 # Locate the median Rrup for each site --------------------------------------------------------------------------------- 1434 median_rrups = np.median(rrups, axis=0) 1435 median_rrup_indices = v_get_median_index(np.transpose(rrups)) 1436 1437 # Locate the simulation that minimizes the difference between median Rrup and Rrup across all sites -------------------- 1438 sum_of_difference_squared = np.sum((median_rrups - rrups) ** 2, axis=1) 1439 median_simulation = np.argmin(sum_of_difference_squared) 1440 1441 # Convert simulated faults xy to ll using haversine formulation -------------------------------------------------------- 1442 lon1s, lat1s = v_XY2LL(elon, elat, rpx1s, rpy1s) 1443 lon2s, lat2s = v_XY2LL(elon, elat, rpx2s, rpy2s) 1444 lon3s, lat3s = v_XY2LL(elon, elat, rpx3s, rpy3s) 1445 lon4s, lat4s = v_XY2LL(elon, elat, rpx4s, rpy4s) 1446 1447 # Convert radians to degrees ------------------------------------------------------------------------------------------- 1448 strikes = np.round(strikes * 180 / np.pi, 6) 1449 dips = np.round(dips * 180 / np.pi, 6) 1450 1451 # Return everything ---------------------------------------------------------------------------------------------------- 1452 return ( 1453 median_simulation, 1454 along_strikes, 1455 down_dips, 1456 lon1s, 1457 lat1s, 1458 rpz1s, 1459 lon2s, 1460 lat2s, 1461 rpz2s, 1462 lon3s, 1463 lat3s, 1464 rpz3s, 1465 lon4s, 1466 lat4s, 1467 rpz4s, 1468 strikes, 1469 dips, 1470 rakes, 1471 models, 1472 areas, 1473 aspect_ratios, 1474 lengths, 1475 widths, 1476 top_depths, 1477 bottom_depths, 1478 )
Simulate earthquake rupture surface that minimizes the difference between the median distance of a pseudo-grid of sites and a stochastic set of possible ruptures. Randomized fault attributes are dependent on the fault category. Fault scaling models depend on earthquake type. Adapted from the CCLD routine originally coded in Frortran by Chiou and Youngs (2008).
Input Arguments:
- eqid = unique integer identifier for the event (to set the random seed)
- eqType = type of earthquake: "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
- region = geographic region where the earthquake occured" "japan" "chile" "other"
- elat = hypocenter latitude (degrees)
- elon = hypocenter longiude (degrees)
- hypd = hypocenter depth (km); positive into the ground
- magnitude = earthquake moment magnitude (Mw)
- method = code for rupture simulation constraints "A" = strike, dip, and rake for first nodal plane solution preferred (optional "strike", "dip", and "rake" arguments are required) Warning: not recommended "B" = strike, dip, and rake for second nodal plane solution preferred (optional "strike2", "dip2", and "rake2" arguments are required) Warning: not recommended "C" = strike, dip, and rake are known for two nodal planes, and neither is preferred (optional "strike", "dip", "rake", "strike2", "dip2", and "rake2" arguments are required) "D" = One nodal plane solution for strike, dip, and rake; randomize the strike and dip (optional "strike", "dip", and "rake" arguments are required) Warning: not recommended "E" = No nodal plane solutions; randomize strike, dip, and rake (dip and rake are assigned based on faulting mechanism) (if optional "mechanism" argument is not speficied, simulations randomly assign one)
- nsims = Number of simulations assigned to each M-scaling relationship. Total number of simulations should be odd. nsims[0] = Wells & Coppersmith (1994) - [recommended 334] nsims[1] = Leonard (2014) - [recommended 333] nsims[2] = Thingbaijam et al. (2017) [recommended 333] nsims[3] = Chiou & Youngs (2008) aspect ratio model with Wells & Coppersmith (1994) area relationship [recommended 111] nsims[4] = Chiou & Youngs (2008) aspect ratio model with Leonard (2014) area relationship [recommended 111] nsims[5] = Chiou & Youngs (2008) aspect ratio model with Thingbaijam et al. (2017) area relationship [recommended 111] nsims[6] = Contreras et al. (2022) - [recommended 333]
- mechanim = known or preferred style-of-faulting [default None] "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) "NM" = normal: -150 < rake < -30 "RV" = reverse: 30 < rake < 150
- strike = strike-angle (degrees) of the first nodal plane solution [default None]
- dip = dip-angle (degrees) of the first nodal plane solution [default None]
- rake = rake-angle (degrees) of the first nodal plane solution [default None]
- strike2 = strike-angle (degrees) of the second nodal plane solution [default None]
- dip2 = dip-angle (degrees) of the second nodal plane solution [default None]
- rake2 = rake-angle (degrees) of the second nodal plane solution [default None]
Output Arguments:
- median_simulation = index of selected (median) rupture
- along_strikes = numpy array of the hypocenter along-strike relative position [0, 1]
- down_dips = numpy array of the hypocenter down-dip relative position [0, 1]
- lon1s = numpy array of longitude (degrees) for the ULC rupture vertex
- lat1s = numpy array of latitude (degrees) for the ULC rupture vertex
- rpz1s = numpy array of depth (km) for the ULC rupture vertex
- lon2s = numpy array of longitude (degrees) for the URC rupture vertex
- lat2s = numpy array of latitude (degrees) for the URC rupture vertex
- rpz2s = numpy array of depth (km) for the URC rupture vertex
- lon3s = numpy array of longitude (degrees) for the LLC rupture vertex
- lat3s = numpy array of latitude (degrees) for the LLC rupture vertex
- rpz3s = numpy array of depth (km) for the LLC rupture vertex
- lon4s = numpy array of longitude (degrees) for the LRC rupture vertex
- lat4s = numpy array of latitude (degrees) for the LRC rupture vertex
- rpz4s = numpy array of depth (km) for the LRC rupture vertex
- strikes = numpy array of strike angle (degrees)
- dips = numpy array of dip angle (degrees)
- rakes = numpy array of rake angle (degrees)
- models = numpy array of magnitude-scaling relationship used for each realization
- areas = numpy array of area (km^2)
- aspect_ratios = numpy array of aspect ratio
- lengths = numpy array of rupture length (km)
- widths = numpy array of rupture width (km)
- top_depths = numpy array of top depth (km) of rupture
- bottom_depths = numpy array of bottom depth (km) of rupture
1481def simulate_rupture_surface( 1482 eqid, 1483 eqType, 1484 region, 1485 elat, 1486 elon, 1487 hypd, 1488 magnitude, 1489 method, 1490 nsims, 1491 mechanism=None, 1492 strike=None, 1493 dip=None, 1494 rake=None, 1495 strike2=None, 1496 dip2=None, 1497 rake2=None, 1498): 1499 """ 1500 Simulate earthquake rupture surface that minimizes the difference between the median distance of a pseudo-grid of sites 1501 and a stochastic set of possible ruptures. Randomized fault attributes are dependent on the fault category. Fault scaling 1502 models depend on earthquake type. Adapted from the CCLD routine originally coded in Frortran by Chiou and Youngs (2008). 1503 1504 Input Arguments: 1505 - eqid = unique integer identifier for the event (to set the random seed) 1506 - eqType = type of earthquake: 1507 "crustal" = shallow-crustal in active tectonic regimes 1508 "intraslab" = intraslab-type in subduction regimes 1509 "interface" = interface-type in subduction regimes 1510 "stable" = shallow-crustal in stable-continental regimes 1511 - region = geographic region where the earthquake occured" 1512 "japan" 1513 "chile" 1514 "other" 1515 - elat = hypocenter latitude (degrees) 1516 - elon = hypocenter longiude (degrees) 1517 - hypd = hypocenter depth (km); positive into the ground 1518 - magnitude = earthquake moment magnitude (Mw) 1519 - method = code for rupture simulation constraints 1520 "A" = strike, dip, and rake for first nodal plane solution preferred 1521 (optional "strike", "dip", and "rake" arguments are required) 1522 Warning: not recommended 1523 "B" = strike, dip, and rake for second nodal plane solution preferred 1524 (optional "strike2", "dip2", and "rake2" arguments are required) 1525 Warning: not recommended 1526 "C" = strike, dip, and rake are known for two nodal planes, and neither is preferred 1527 (optional "strike", "dip", "rake", "strike2", "dip2", and "rake2" arguments are required) 1528 "D" = One nodal plane solution for strike, dip, and rake; randomize the strike and dip 1529 (optional "strike", "dip", and "rake" arguments are required) 1530 Warning: not recommended 1531 "E" = No nodal plane solutions; randomize strike, dip, and rake 1532 (dip and rake are assigned based on faulting mechanism) 1533 (if optional "mechanism" argument is not speficied, simulations randomly assign one) 1534 - nsims = Number of simulations assigned to each M-scaling relationship. Total number of simulations should be odd. 1535 nsims[0] = Wells & Coppersmith (1994) - [recommended 334] 1536 nsims[1] = Leonard (2014) - [recommended 333] 1537 nsims[2] = Thingbaijam et al. (2017) [recommended 333] 1538 nsims[3] = Chiou & Youngs (2008) aspect ratio model with Wells & Coppersmith (1994) area relationship [recommended 111] 1539 nsims[4] = Chiou & Youngs (2008) aspect ratio model with Leonard (2014) area relationship [recommended 111] 1540 nsims[5] = Chiou & Youngs (2008) aspect ratio model with Thingbaijam et al. (2017) area relationship [recommended 111] 1541 nsims[6] = Contreras et al. (2022) - [recommended 333] 1542 - mechanim = known or preferred style-of-faulting [default None] 1543 "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) 1544 "NM" = normal: -150 < rake < -30 1545 "RV" = reverse: 30 < rake < 150 1546 - strike = strike-angle (degrees) of the first nodal plane solution [default None] 1547 - dip = dip-angle (degrees) of the first nodal plane solution [default None] 1548 - rake = rake-angle (degrees) of the first nodal plane solution [default None] 1549 - strike2 = strike-angle (degrees) of the second nodal plane solution [default None] 1550 - dip2 = dip-angle (degrees) of the second nodal plane solution [default None] 1551 - rake2 = rake-angle (degrees) of the second nodal plane solution [default None] 1552 1553 Output Objects: 1554 - SIMULATIONS = pandas DataFrame object containing all simulated rupture surfaces 1555 - SELECTED = pandas DataFrame object containing the selected rupture surface and statistics 1556 """ 1557 1558 ( 1559 median_simulation, 1560 along_strikes, 1561 down_dips, 1562 lon1s, 1563 lat1s, 1564 rpz1s, 1565 lon2s, 1566 lat2s, 1567 rpz2s, 1568 lon3s, 1569 lat3s, 1570 rpz3s, 1571 lon4s, 1572 lat4s, 1573 rpz4s, 1574 strikes, 1575 dips, 1576 rakes, 1577 models, 1578 areas, 1579 aspect_ratios, 1580 lengths, 1581 widths, 1582 top_depths, 1583 bottom_depths, 1584 ) = get_rupture_surface_simulations( 1585 eqid, 1586 eqType, 1587 region, 1588 elat, 1589 elon, 1590 hypd, 1591 magnitude, 1592 method, 1593 nsims, 1594 mechanism, 1595 strike, 1596 dip, 1597 rake, 1598 strike2, 1599 dip2, 1600 rake2, 1601 ) 1602 1603 # Compute the statistics of the simulation results ---------------------------------------------------------------------- 1604 areas_avg = 10 ** np.mean(np.log10(areas)) 1605 areas_std = 10 ** np.std(np.log10(areas)) 1606 aspect_ratios_avg = 10 ** np.mean(np.log10(aspect_ratios)) 1607 aspect_ratios_std = 10 ** np.std(np.log10(aspect_ratios)) 1608 lengths_avg = 10 ** np.mean(np.log10(lengths)) 1609 lengths_std = 10 ** np.std(np.log10(lengths)) 1610 widths_avg = 10 ** np.mean(np.log10(widths)) 1611 widths_std = 10 ** np.std(np.log10(widths)) 1612 1613 max_top_depths = np.max(top_depths) 1614 min_top_depths = np.min(top_depths) 1615 max_bottom_depths = np.max(bottom_depths) 1616 min_bottom_depths = np.min(bottom_depths) 1617 1618 # Construct pandas DataFrame objects to package the results ------------------------------------------------------------- 1619 total_sims = np.sum(nsims) 1620 SIMULATIONS = pd.DataFrame( 1621 { 1622 "Simulation": np.array(range(total_sims)) + 1, 1623 "EQID": np.repeat(eqid, total_sims), 1624 "Magnitude": np.repeat(magnitude, total_sims), 1625 "Hypocenter Longitude": np.repeat(elon, total_sims), 1626 "Hypocenter Latitude": np.repeat(elat, total_sims), 1627 "Hypocenter Depth (km)": np.repeat(hypd, total_sims), 1628 "Hypocenter Along-Strike Position": along_strikes, 1629 "Hypocenter Down-Dip Position": down_dips, 1630 "ULC Longitude": lon2s, 1631 "ULC Latitude": lat2s, 1632 "ULC Depth (km)": rpz2s, 1633 "URC Longitude": lon1s, 1634 "URC Latitude": lat1s, 1635 "URC Depth (km)": rpz1s, 1636 "LRC Longitude": lon3s, 1637 "LRC Latitude": lat3s, 1638 "LRC Depth (km)": rpz3s, 1639 "LLC Longitude": lon4s, 1640 "LLC Latitude": lat4s, 1641 "LLC Depth (km)": rpz4s, 1642 "Strike": strikes, 1643 "Dip": dips, 1644 "Rake": rakes, 1645 "Scaling Relation": models, 1646 "Area (km^2)": areas, 1647 "Aspect Ratio": aspect_ratios, 1648 "Rupture Length (km)": lengths, 1649 "Rupture Width (km)": widths, 1650 "Rupture Top Depth (km)": top_depths, 1651 "Rupture Bottom Depth (km)": bottom_depths, 1652 } 1653 ) 1654 1655 SELECTED = pd.DataFrame( 1656 { 1657 "Median Simulation": [median_simulation + 1], 1658 "EQID": [eqid], 1659 "Magnitude": [magnitude], 1660 "Hypocenter Longitude": [elon], 1661 "Hypocenter Latitude": [elat], 1662 "Hypocenter Depth (km)": [hypd], 1663 "Hypocenter Along-Strike Position": [along_strikes[median_simulation]], 1664 "Hypocenter Down-Dip Position": [down_dips[median_simulation]], 1665 "ULC Longitude": [lon2s[median_simulation]], 1666 "ULC Latitude": [lat2s[median_simulation]], 1667 "ULC Depth (km)": [rpz2s[median_simulation]], 1668 "URC Longitude": [lon1s[median_simulation]], 1669 "URC Latitude": [lat1s[median_simulation]], 1670 "URC Depth (km)": [rpz1s[median_simulation]], 1671 "LRC Longitude": [lon3s[median_simulation]], 1672 "LRC Latitude": [lat3s[median_simulation]], 1673 "LRC Depth (km)": [rpz3s[median_simulation]], 1674 "LLC Longitude": [lon4s[median_simulation]], 1675 "LLC Latitude": [lat4s[median_simulation]], 1676 "LLC Depth (km)": [rpz4s[median_simulation]], 1677 "Strike": [strikes[median_simulation]], 1678 "Dip": [dips[median_simulation]], 1679 "Rake": [rakes[median_simulation]], 1680 "Scaling Relation": [models[median_simulation]], 1681 "Area (km^2)": [areas[median_simulation]], 1682 "Average Area (km^2)": [areas_avg], 1683 "Area Standard Deviation (km^2)": [areas_std], 1684 "Aspect Ratio": [aspect_ratios[median_simulation]], 1685 "Average Aspect Ratio": [aspect_ratios_avg], 1686 "Aspect Ratio Standard Deviation": [aspect_ratios_std], 1687 "Length (km)": [lengths[median_simulation]], 1688 "Average Length (km)": [lengths_avg], 1689 "Length Standard Deviation (km)": [lengths_std], 1690 "Width (km)": [widths[median_simulation]], 1691 "Average Width (km)": [widths_avg], 1692 "Width Standard Deviation (km)": [widths_std], 1693 "Rupture Top Depth (km)": [top_depths[median_simulation]], 1694 "Rupture Bottom Depth (km)": [bottom_depths[median_simulation]], 1695 } 1696 ) 1697 1698 return (SIMULATIONS, SELECTED)
Simulate earthquake rupture surface that minimizes the difference between the median distance of a pseudo-grid of sites and a stochastic set of possible ruptures. Randomized fault attributes are dependent on the fault category. Fault scaling models depend on earthquake type. Adapted from the CCLD routine originally coded in Frortran by Chiou and Youngs (2008).
Input Arguments:
- eqid = unique integer identifier for the event (to set the random seed)
- eqType = type of earthquake: "crustal" = shallow-crustal in active tectonic regimes "intraslab" = intraslab-type in subduction regimes "interface" = interface-type in subduction regimes "stable" = shallow-crustal in stable-continental regimes
- region = geographic region where the earthquake occured" "japan" "chile" "other"
- elat = hypocenter latitude (degrees)
- elon = hypocenter longiude (degrees)
- hypd = hypocenter depth (km); positive into the ground
- magnitude = earthquake moment magnitude (Mw)
- method = code for rupture simulation constraints "A" = strike, dip, and rake for first nodal plane solution preferred (optional "strike", "dip", and "rake" arguments are required) Warning: not recommended "B" = strike, dip, and rake for second nodal plane solution preferred (optional "strike2", "dip2", and "rake2" arguments are required) Warning: not recommended "C" = strike, dip, and rake are known for two nodal planes, and neither is preferred (optional "strike", "dip", "rake", "strike2", "dip2", and "rake2" arguments are required) "D" = One nodal plane solution for strike, dip, and rake; randomize the strike and dip (optional "strike", "dip", and "rake" arguments are required) Warning: not recommended "E" = No nodal plane solutions; randomize strike, dip, and rake (dip and rake are assigned based on faulting mechanism) (if optional "mechanism" argument is not speficied, simulations randomly assign one)
- nsims = Number of simulations assigned to each M-scaling relationship. Total number of simulations should be odd. nsims[0] = Wells & Coppersmith (1994) - [recommended 334] nsims[1] = Leonard (2014) - [recommended 333] nsims[2] = Thingbaijam et al. (2017) [recommended 333] nsims[3] = Chiou & Youngs (2008) aspect ratio model with Wells & Coppersmith (1994) area relationship [recommended 111] nsims[4] = Chiou & Youngs (2008) aspect ratio model with Leonard (2014) area relationship [recommended 111] nsims[5] = Chiou & Youngs (2008) aspect ratio model with Thingbaijam et al. (2017) area relationship [recommended 111] nsims[6] = Contreras et al. (2022) - [recommended 333]
- mechanim = known or preferred style-of-faulting [default None] "SS" = strike-slip: (-180 < rake < -150) or (-30 < rake < 30) or (150 < rake < 180) "NM" = normal: -150 < rake < -30 "RV" = reverse: 30 < rake < 150
- strike = strike-angle (degrees) of the first nodal plane solution [default None]
- dip = dip-angle (degrees) of the first nodal plane solution [default None]
- rake = rake-angle (degrees) of the first nodal plane solution [default None]
- strike2 = strike-angle (degrees) of the second nodal plane solution [default None]
- dip2 = dip-angle (degrees) of the second nodal plane solution [default None]
- rake2 = rake-angle (degrees) of the second nodal plane solution [default None]
Output Objects:
- SIMULATIONS = pandas DataFrame object containing all simulated rupture surfaces
- SELECTED = pandas DataFrame object containing the selected rupture surface and statistics