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)
TECTONIC_MAPPING = {'Crustal': 'crustal', 'Slab': 'intraslab', 'Interface': 'interface', 'Outer-rise': 'intraslab', 'Undetermined': 'crustal'}
def WellsCoppersmith1994(magnitude, eqType):
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)
def Leonard2014(magnitude, mechanism, eqType):
 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)
def ThingbaijamEtAl2017(magnitude, mechanism, eqType):
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)
def ChiouYoungs2008(magnitude, mechanism, eqType, model):
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)
def ContrerasEtAl2022(magnitude, eqType):
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)
def get_mechanism_based_on_rake(rake):
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

def discrete(n, x, p, tp):
468def discrete(n, x, p, tp):
469    for i in range(10):
470        if (tp >= p[i]) and (tp <= p[i + 1]):
471            tx = x[i] + (x[i + 1] - x[i]) * (tp - p[i]) / (p[i + 1] - p[i])
472            return tx
473    return
def get_hyp_down_dip_position(eqtype, region):
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
def get_hyp_along_strike_position(eqtype):
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
def get_median_index(array):
562def get_median_index(array):
563    index = np.argpartition(array, len(array) // 2)[len(array) // 2]
564    return index
def LL2XY(zlon, zlat, lon, lat):
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

def XY2LL(zlon, zlat, x, y):
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)
def pointTriangleDistance(TRI_xyz, P_xyz):
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
def check_input_arguments(eqType, method, nsims, strike, dip, rake, strike2, dip2, rake2):
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                )
v_WellsCoppersmith1994 = <numpy.vectorize object>
v_Leonard2014 = <numpy.vectorize object>
v_ThingbaijamEtAl2017 = <numpy.vectorize object>
v_ChiouYoungs2008 = <numpy.vectorize object>
v_ContrerasEtAl2022 = <numpy.vectorize object>
v_get_mechanism_based_on_rake = <numpy.vectorize object>
v_get_hyp_down_dip_position = <numpy.vectorize object>
v_get_hyp_along_strike_position = <numpy.vectorize object>
v_get_median_index = <numpy.vectorize object>
v_LL2XY = <numpy.vectorize object>
v_XY2LL = <numpy.vectorize object>
def get_rupture_surface_simulations( eqid, eqType, region, elat, elon, hypd, magnitude, method, nsims, mechanism=None, strike=None, dip=None, rake=None, strike2=None, dip2=None, rake2=None):
 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
def simulate_rupture_surface( eqid, eqType, region, elat, elon, hypd, magnitude, method, nsims, mechanism=None, strike=None, dip=None, rake=None, strike2=None, dip2=None, rake2=None):
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