§ 1Resumen ejecutivo
El motor reproduce con precisión < 5 % los 12 casos del PEER PSHA Verification Project Set 1 (Thomas, Wong, Abrahamson, 2010) — el estándar de facto para calibrar códigos de PSHA. La validación cubre falla cortical vertical y buzante, cuatro MFDs (delta, trunc exp, trunc normal, Y&C 1985), variabilidad de área de ruptura, tres regímenes de truncación de sigma y sources areales/volumétricos.
one_sided R-CRISIS · symmetric PEER · median_only σ=0.§ 2Integral Cornell-McGuire
Para cada sitio el motor calcula la tasa anual media de excedencia
mfd.nu).mfd.sample_magnitudes() → (centers, probs) con ancho de bin 0.01–0.1 Mw según el MFD.SourceDistance(distance_km, probability, depth_km), por magnitud (floating rupture).La integral se vectoriza sobre los IML en PSHACalculator.compute_hazard_curve(), dando ~30× speed-up frente al loop por nivel. Periodo de retorno vía λ = −ln(1 − P) / T.
§ 3Modelo de source
La clase central es SeismicSource (hazard_curve.py:216):
| Campo | Tipo | Propósito |
|---|---|---|
| mfd | TruncatedGR · Characteristic · Delta · Y&C85 · TruncNormal | Distribución de magnitud-frecuencia. |
| distances | list[SourceDistance] | Distribución de distancia base. Usada cuando no hay mag_distances. |
| mag_distances | dict[float, list[SourceDistance]] | Floating rupture: cada bin de magnitud ve su propia distribución de distancia, calculada sobre la ruptura de ese tamaño. |
| mechanism | strike_slip · reverse · normal · subduction | Tipo de falla. La GMPE aplica correcciones (p.ej. 1.2 reverse factor en Sadigh). |
| tectonic | crustal · interface · intraslab | Selecciona qué ensemble de GMPEs usar y qué sigma_trunc aplicar. |
| hypo_depth | float (km) | Profundidad hypocentral. Usada por GMPEs de subducción (BCHydro). |
| backarc, vs30_override, hw_factor | auxiliares | Flags específicos por region/GMPE. |
SourceDistance.probability siempre suma 1.0 en cada lista (base y por-magnitud), así que el motor ve una distribución propiamente normalizada.
Builder público para fallas planares
El camino recomendado para construir una source planar es build_planar_fault_source() (peer.py). Precomputa las distancias flotantes por magnitud y las conecta a mag_distances:
# Ejemplo: San Andreas segmento central from pipeline.psha.peer import ( PEERTruncatedExponentialMFD, build_planar_fault_source, ) mfd = PEERTruncatedExponentialMFD( b_value=0.9, m_min=5.0, m_max=7.0, area_km2=fault.length_km * fault.width_km, ) source = build_planar_fault_source( source_id="SAF-central", name="San Andreas central", mfd=mfd, mechanism="strike_slip", tectonic="crustal", site_lat=site.lat, site_lon=site.lon, trace_s=(fault.south_lat, fault.south_lon), trace_n=(fault.north_lat, fault.north_lon), fault_top_km=0.0, fault_bot_km=12.0, dip_deg=90.0, dip_direction="east", fault_length_km=fault.length_km, )
Desde source_service.discretize_source() la selección es automática: si la traza tiene profundidad uniforme (falla cortical planar) se usa floating rupture; si varía con la profundidad (slab de subducción) cae al mesh 3-D existente.
§ 4Floating rupture
Para cada magnitud m el motor calcula las dimensiones de ruptura con las relaciones PEER:
La ruptura se mueve uniformemente sobre el plano con regla del punto medio — positions (i + 0.5)·Δ para i ∈ [0, n − 1] — tanto en strike como en down-dip. Cada posición recibe peso 1/n_total. Esto evita el sesgo de borde del trapezoidal endpoint-inclusive.
rx=0 independientemente del desplazamiento en down-dip. Para fallas buzantes esto subestima Rrup en sitios del footwall y sobrestima en sitios directamente arriba. Test 1.4 (PEER Fault 2, reverse 60° W) fallaba con 170 % de error en Site 7 (footwall) hasta que incorporamos el top_horiz_offset_km = d_off · cos(dip) en _rrup_planar().
§ 5Distribuciones de magnitud
Todos los MFDs implementan la misma interfaz .nu (tasa anual total) y .sample_magnitudes() → (centers, probs), así que son drop-in entre sí.
| Clase | Archivo | Caso PEER | Descripción |
|---|---|---|---|
| TruncatedGR | hazard_curve.py | legacy | Gutenberg-Richter doblemente acotado. ν = 10(a − b·Mmin). Usado para sources con catálogo histórico. |
| CharacteristicEarthquake | hazard_curve.py | legacy | Gaussiana estrecha alrededor de Mchar. No reproduce Y&C 1985 — para PEER usar YoungsCoppersmith1985MFD. |
| DeltaMFD | peer.py | 1.1 · 1.2 · 1.4 · 1.8 | Función delta en una magnitud. Tasa vía balance de momento sísmico a partir de slip rate. |
| PEERTruncatedExponentialMFD | peer.py | 1.5 | Exponencial truncada. Balance de momento integrado desde M = 0 (convención PEER). |
| PEERTruncatedNormalMFD | peer.py | 1.6 | Normal truncada solo en el extremo superior (Mmax). Balance de momento desde M = 0. |
| YoungsCoppersmith1985MFD | peer.py | 1.7 | Cola exponencial + boxcar elevado. Densidad del boxcar = densidad exp extrapolada en M = Mbox_lo − ΔM₂ con ΔM₂ = 1.0 (Y&C original). |
Convenciones PEER bakeadas en los MFDs
- Balance de momento integrado desde M = 0, no desde Mmin.
log₁₀(M₀) = 1.5·M + 16.05(no 16.1).- μ (shear modulus) =
3 × 10¹¹dyne/cm². - Y&C 1985: el boxcar tiene densidad elevada por factor
exp(β·ΔM₂)respecto a la extrapolación continua de la exp. Este fue otro bug corregido — la implementación previa usaba densidad continua en la frontera, dando ~3× menos tasa.
§ 6Ground Motion Prediction Equations
Las GMPEs implementan la signatura compute(magnitude, distance_km, vs30, period, mechanism, **kwargs) → (ln_mean, ln_sigma).
| Familia | GMPE | Archivo | Uso |
|---|---|---|---|
| NGA-West2 | Abrahamson, Silva, Kamai (2014) | abrahamson_2014.py | crustal (ensemble 25 %) |
| Boore, Stewart, Seyhan, Atkinson (2014) | boore_2014.py | crustal (ensemble 25 %) | |
| Campbell & Bozorgnia (2014) | campbell_bozorgnia_2014.py | crustal (ensemble 25 %) | |
| Chiou & Youngs (2014) | chiou_youngs_2014.py | crustal (ensemble 25 %) | |
| Subducción | BCHydro 2016 — interface | bchydro_2016.py | subducción interplaca |
| BCHydro 2016 — intraslab | bchydro_2016.py | subducción intraslab | |
| Legacy | Sadigh et al. (1997) | sadigh_1997.py | referencia PEER Set 1 |
gmpes/sadigh_1997.py.
§ 7Truncación de sigma
Parámetro PSHACalculator(sigma_truncation_mode=...):
| Modo | Fórmula | Uso |
|---|---|---|
one_sided default |
sf(z) si z ≤ k, 0 si z > k | Convención R-CRISIS. Preserva la tasa absoluta a IMLs bajos. Compatibilidad con PEM1 (ASK14 sin truncar + BCHydro a 3σ). |
symmetric |
(Φ(k) − Φ(z)) / (2Φ(k) − 1) | PEER 2010/106 Cases 1.8b, 1.8c. Renormalización simétrica en [−k, k]: 1 debajo, 0 arriba. |
median_only |
step (1 si IML ≤ mediano, si no 0) | Límite σ = 0. PEER 2010/106 Cases 1.1 – 1.7 (σ = 0 hard-coded). |
Convención R-CRISIS vs PEER
R-CRISIS usa one-sided (trunca arriba, no renormaliza). PEER 2010/106 Cases 1.8b/c usan symmetric con renormalización. Ambas dan respuestas diferentes cuando k es finito; el motor soporta las dos y deja la elección al caller vía parámetro. El default es one-sided para mantener compatibilidad con proyectos existentes calibrados contra R-CRISIS (p.ej. Manila 5BGC).
Para el caso σ = 0 (medianas sin variabilidad aleatoria) añadimos median_only porque ninguna de las dos fórmulas anteriores converge correctamente al límite σ → 0 con renormalización finita.
§ 8Validación PEER 2010/106
Los benchmarks vienen del Excel oficial del proyecto PEER (docs/Tests/Set 1 Results.xlsm), parseado por docs/Tests/parse_peer_tests.py. Estos valores superan a las tablas del PDF publicado — en particular, los Cases 1.10/1.11 fueron regenerados en el Excel con sigma untruncated aunque el texto del PDF aún dice "σ = 0", algo que confirmamos empíricamente comparando con todos los 13 códigos participantes.
| Caso | Descripción | bins pass | peor Δ | runtime |
|---|---|---|---|---|
| 1.1 | M=6.5 delta, ruptura completa | 71 / 71 | 0.14 % | 0.0 s |
| 1.2 | M=6.0 delta, floating rupture | 57 / 57 | 2.68 % | 0.1 s |
| 1.3 | M=6.0 con σlog10 A=0.25 | 57 / 57 | 4.49 % | 3.3 s |
| 1.4 | M=6.0 en falla reverse 60° W | 61 / 61 | 4.30 % | 0.7 s |
| 1.5 | MFD truncated exponential | 65 / 65 | 2.09 % | 38.0 s |
| 1.6 | MFD truncated normal | 65 / 65 | 0.73 % | 39.6 s |
| 1.7 | Youngs & Coppersmith (1985) | 65 / 65 | 2.83 % | 38.7 s |
| 1.8a | Sadigh σ untruncated | 119 / 119 | 2.49 % | 0.6 s |
| 1.8b | Truncación simétrica ±2σ | 98 / 98 | 2.28 % | 1.9 s |
| 1.8c | Truncación simétrica ±3σ | 113 / 113 | 3.29 % | 1.9 s |
| 1.10 | Source areal, profundidad fija 5 km | 70 / 70 | 3.11 % | 29.9 s |
| 1.11 | Source volumétrico, 5–10 km | 69 / 69 | 4.20 % | 145.3 s |
| TOTAL | 12 casos, 7 sitios cada uno para el fault suite · 4 para area | 910 / 910 | 4.49 % | 5:00 min |
Bugs detectados y corregidos durante la validación
- Sadigh 1997 sin split M ≤ 6.5 / M > 6.5 — coeficientes unificados. Afecta aplicaciones con Mmax > 6.5.
- Rrup de falla buzante — el borde superior de la ruptura se pinaba en
rx=0sin incluir el offset horizontal cuando la ruptura flotaba down-dip. Error de hasta 170 % en footwall sites. - Youngs & Coppersmith 1985 sin elevación del boxcar — densidad continua en la frontera en vez de la elevada por
exp(β·ΔM₂). Resultado: 3× menos tasa, 0 / 65 passing inicialmente. - Floating rupture endpoint-inclusive — sesgo en sitios de borde por usar trapezoidal en vez de midpoint rule. Corregido pasando a midpoint.
- Truncación de sigma sin renormalización simétrica — faltaban los modos
symmetricymedian_only. - Piso de distancia 1 km forzado en
compute_hazard_curve— mataba la cola de near-fault cuando la GMPE ya cap-ea a 0.1 km por sí sola. Ahoramin_distance_km=0.1. - MFD truncated normal con balance de momento sobre [Mmin, Mmax] — PEER integra desde M = 0. Corregido en
PEERTruncatedNormalMFD. - Grilla de area source en coord. esférica sin cos(lat) — ligera anisotropía en la densidad de puntos. Corregido en
area_source_distancescon spacing real 1 km.
Cómo correr la suite
# Suite PEER completa con tabla de salida uv run --project packages/pipeline python docs/Tests/run_peer_tests.py # Suite PEER via pytest (idéntica lógica, formato pytest) pytest tests/pipeline/psha/test_peer_verification.py --run-slow -q # Suite rápida (PEER oculto, smoke tests de producción incluidos) pytest tests/pipeline -q
§ 9Regresión Manila 5BGC
Snapshot blindado contra drift silencioso en el caso con el que calibramos el motor: 5 BGC, Taguig, Metro Manila (14.54762°N, 121.04674°E, Vs30 = 520 m/s), modelo de sources PEM1 de Dynamis Associates × Appgile cargado directamente desde packages/pipeline/data/PSHA_PEM1.DAT (41 sources tras el filtro de catalog_row_to_source). Sin base de datos, sin GEM API, sin R-CRISIS — 100 % offline.
f_depth en BCHydro intraslab cuando ya está en el Rrup 3-D. El baseline nuevo colapsa el slab a < 1 % y el hazard pasa a dominarse por los segmentos del Manila trench (interface) y la West Valley Fault — físicamente consistente con un sitio a 1.1 km de Marikina.
| RP (yr) | Baseline 03-31 | Baseline 04-13 | Dynamis (RotD100) | Ratio vs Dynamis |
|---|---|---|---|---|
| 43 (SLE) | 0.11923 g | 0.11509 g | 0.154 g | 0.75 × |
| 475 (DBE) | 0.35271 g | 0.33597 g | — | — |
| 975 | 0.46724 g | 0.44323 g | — | — |
| 2475 (MCEr) | 0.65334 g | 0.61688 g | 1.004 g | 0.61 × |
El motor calcula RotD50 (mediana direccional) y Dynamis reporta RotD100 (dirección de máxima respuesta). La ratio típica RotD100/RotD50 es 1.1–1.3, lo que explica la mayoría del 25-40 % de delta contra Dynamis. Más adelante se añadirán el término de directividad Bayless-Somerville (2013) y el floor del 80 % del código (TBI 2017) que Dynamis aplica.
Top 5 contribuciones (RP = 475 yr)
| Source | Tectonic | Contribución |
|---|---|---|
| Manila trench south EXP | interface | 37.2 % |
| Zone 6 (area source) | crustal | 22.0 % |
| Marikina Valley West | crustal | 13.0 % |
| Manila trench south | interface | 12.4 % |
| Marikina Valley East | crustal | 8.2 % |
Test
3 aserciones en tests/pipeline/psha/test_manila_regression.py:
- source_count — 41 sources (a drift aquí señalaría un cambio en el parser de PEM1 DAT).
- design_pga — PGA a 43 / 475 / 975 / 2475 yr dentro de ±0.5 % del snapshot.
- top_source_contributions — los 5 sources dominantes y su porcentaje dentro de ±1 punto porcentual.
Marcado como @pytest.mark.slow. Runtime end-to-end 22 s. Opt-in con pytest --run-slow.
§ 10Anchors de GMPE (snapshot regression)
47 puntos de anclaje que fijan el output exacto (ln_mean, ln_sigma) de cada GMPE en tuplas específicas de (M, R, Vs30, T, mechanism). Tolerancia 0.001 en unidades logarítmicas — aprox. 0.1 % en valor lineal. Detecta cualquier drift de coeficiente al instante.
Los tests en tests/pipeline/test_gmpes.py ya existían pero solo verifican rangos amplios ("PGA debería estar entre 0.02 y 0.5 g"). Un bug de coeficientes puede caber holgadamente dentro de esos rangos y pasar desapercibido. Los anchor tests complementan ese chequeo con valores exactos.
| GMPE | Anchors | Cobertura |
|---|---|---|
SadighEtAl1997 |
7 | M = 5 / 6 / 6.5 / 7 / 7.5 · R = 1 / 10 / 30 / 100 / 200 · strike-slip + reverse. Verifica el split M ≤ 6.5 / M > 6.5 que corregimos durante la validación PEER. |
AbrahamsonEtAl2014 (ASK14) | 9 | Incluye SA(0.2) · SA(1.0) · Vs30 = 270 para amplificación en suelo blando. |
BooreEtAl2014 (BSSA14) | 9 | Mismas tuplas + factor de estilo de falla. |
CampbellBozorgnia2014 (CB14) | 9 | Idem. |
ChiouYoungs2014 (CY14) | 9 | Idem. |
BCHydro2016Interface | 2 | Megathrust M 7.5 / 8.5 con hypo_depth_km=30. |
BCHydro2016Intraslab | 2 | Intraslab M 7.0 / 7.5 con hypo_depth_km=80 / 120. |
| TOTAL | 47 | Suite completa en 80 ms (fast). |
§ 11PEER Set 2 y Set 3 — estado
El PEER 2010/106 distribuye tres sets de casos de verificación. Set 1 está completo (§ 8). Set 2 y Set 3 están parcialmente cubiertos. Este es el estado actual y el roadmap.
Set 2 — sources múltiples, logic tree, hanging wall, intraslab
| Sheet | Descripción | Alcanzable hoy |
|---|---|---|
Test 2.1 |
Multiple Sources (Area + Fault B + Fault C, deaggregation) | Reach probe — rate total clava a −1.2 % con Y&C 1985 (Mchar=7.0 y 6.5), area source y Sadigh 97 combinados. La forma de la curva de hazard deriva ~30 % por ambigüedades no resueltas del PDF (widths, depths, convención σ). Implementado en test_peer_set2_case2.py con un test que pasa y otro xfail estricto documentado. |
Test 2.2a-d |
Ensemble individual NGA-West2 (ASK14 / BSSA14 / CB14 / CY14) | Partial — las GMPEs están implementadas y cubiertas por los 47 anchor tests (§ 10), pero las sheets del Excel no documentan la geometría del source. Requiere reverse-engineering del PDF. |
Test 2.3a-d |
Hanging wall effect con cada NGA-West2 | Partial — SeismicSource.hw_factor existe en el engine. Falta la geometría del source. |
Test 2.4a-b |
Distribuciones no-uniformes de hipocentro (uniform / triangular) | Gap — el engine asume hipocentro uniforme (uniform rupture location). Requiere extender floating_rupture_distances con pesos configurables por posición. |
Test 2.5a-b |
Mixture models en la distribución de ground motion (upper tails) | Gap — el engine asume log-normal single; mixture models no están soportados. |
Las 13 hojas de Set 2 sí tienen benchmarks (5 core codes → mean como verdad aceptada, ±5 % por sheet — misma convención que Set 1). La Barrera principal no es el motor sino que la geometría de source está fuera del Excel y hay que reverse-engineer-la del PDF. Plan: tras la reunión con Carlos, abordar Test 2.1 (el más simple) y Test 2.2/2.3 juntos (misma source, distintas GMPEs).
Set 3 — cross-code comparison, sin benchmarks absolutos
Los 5 sheets (Test 3.1 Bending Fault, Test 3.2 Logic tree fractiles, Test 3.3 Intraslab zones, Test 3.4 Areal finite rupture, Test 3.1 GW) son ejercicios de comparación cruzada entre códigos — no hay una respuesta única aceptada. Útiles para detectar desacuerdos, no para certificar corrección absoluta. Por ese motivo Set 3 no forma parte de la suite de validación del motor.
Podrían usarse como chequeo de razonabilidad: si el motor cae dentro del cloud P5-P95 de los 13 códigos participantes en un modelo concreto, es señal de que el motor está en el rango. Esto queda registrado como mejora futura pero sin prioridad.
§ 12Mapa de archivos
packages/pipeline/pipeline/psha/ ├── hazard_curve.py Cornell-McGuire core + PSHACalculator ├── peer.py MFDs PEER + floating rupture + builder público ├── fault_mesh.py 3-D meshing para slabs de subducción ├── gmpes/ │ ├── sadigh_1997.py Sadigh (referencia PEER) │ ├── abrahamson_2014.py ASK14 │ ├── boore_2014.py BSSA14 │ ├── campbell_bozorgnia_2014.py CB14 │ ├── chiou_youngs_2014.py CY14 │ ├── bchydro_2016.py BCHydro interface / intraslab │ └── base.py Protocol GMPE ├── pga_calculator.py helpers PGA site-level └── gmpe_registry.py lookup + pesos por ensemble packages/pipeline/pipeline/services/ ├── source_service.py catalog → SeismicSource pipeline │ # discretize_source ahora auto-selecciona │ # floating rupture vs 3-D mesh └── hazard_service.py capa de orquestación tests/pipeline/psha/ ├── test_peer_verification.py PEER Set 1 (12 casos, --run-slow) ├── test_peer_set2_case2.py Set 2 Case 2 reach probe (rate ±2 %) ├── test_manila_regression.py Manila 5BGC snapshot (--run-slow) ├── test_production_fault_source.py smoke tests end-to-end ├── fixtures/ │ └── manila_5bgc_snapshot.json baseline Apr-13 para regresión └── conftest.py gate --run-slow tests/pipeline/ └── test_gmpe_anchors.py 47 anchors (ln_mean, ln_sigma) por GMPE docs/engine/ ├── README.md overview técnico (este documento en MD) └── psha-engine-review.html versión HTML (este archivo) docs/Tests/ ├── run_peer_tests.py CLI demo 910/910 bins ├── parsed_tests.json benchmarks del Excel PEER └── PEER_2010_106.pdf reporte original
§ 13Limitaciones conocidas
-
Fallas no-planares (multi-segmento con bends).
floating_rupture_fault_discretizeusa el chord entre el primer y último punto de la traza para definir el plano, conservando la longitud de arco real. Para fallas con bends fuertes la aproximación subestima Rrup en sitios cercanos al bend. Extensión futura: floating rupture piecewise-planar. - Aspect ratio de ruptura fijo (L / W = 2). Las escalas PEER están hard-coded. Para fallas con aspect ratios atípicos (normal faults, fuertemente elongadas) se necesitaría parametrizar.
-
Subducción sigue usando mesh 3-D.
Para slabs con traza variable en profundidad el
fault_mesh.mesh_fault_surface_with_ruptures(centroide + hack de reducción de profundidad) todavía no está PEER-verificado. Funciona razonablemente para eventos pequeños pero no tenemos un benchmark formal. -
Set 2 del PEER no validado.
Non-planar faults, logic trees, fractiles, listric faults, zonas intraslab — los benchmarks existen en
Set 2 Results.xlsmpero la definición de la fuente no está en los sheets. Reproducción pendiente.