Skip to content

Classify tectonic type by CatSep (Haynie et al. 2026); remove NSHM 2022 and NGA-SUB rules - #169

Merged
lispandfound merged 6 commits into
mainfrom
haynie-tectonic-type
Oct 27, 2026
Merged

lispandfound merged 6 commits into
mainfrom
haynie-tectonic-type

Conversation

@lispandfound

@lispandfound lispandfound commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Replaces the tectonic type rules in source_modelling/focal_mechanism.py with the CatSep plausibility scores of Haynie et al. (2026, SRL 97(2A), doi:10.1785/0220250078), following software release v1.0.0 (DOI-USGS/neic-catalog-separation@1.0.0). Everything sits behind the existing public API.

Within the Slab2 footprint, all ramps are from eq. 1 and Table 1 of the paper:

  • P_interface = p_flex · p_sz · p_kagan
    • p_flex is 1 within ±15 km of the slab surface (the "flex zone").
    • p_sz ramps from 1 at the seismogenic zone depth to 0 at 15 km below it. The depths are from Hayes et al. (2018): Kermadec–Hikurangi 47 km, Puysegur 30 km.
    • p_kagan ramps from 1 at a Kagan angle of 30° to 0.25 at 53°. The angle is measured against slip on the slab plane at the region's seismogenic rake (93° for Kermadec, CatSep's 90° default for Puysegur). The term is dropped when there is no mechanism.
  • P_crustal = (1 − P_interface) · ramp(depth − slab depth: 1 at −20 km → 0 at +20 km) · p_moho
  • P_slab takes the remainder.
  • Outside the footprint, events are crustal, as before (CatSep's non-NSHM mode).

Moho: CatSep's CRUST1.0 depth-to-Moho grid (Laske et al. 2013), cropped to NZ (NZ_CFM/crust1_moho_nz.npz, 2.7 kB, 1° cells 54.5–24.5°S, 156.5–189.5°E), is used through MohoModel. Outside that grid there is no Moho (∞): the crust extends to the slab. Not implemented: the overturned-slab supplement, the mantle flag and the A–D quality grades.

API

Stable: CMTClassifier.tectonic_type(centroid, nodal_plane_1, nodal_plane_2), the call workflow's gcmt-to-realisation (and so the realtime simulation workflow) makes, and TectonicType. The planes are now optional. The Kagan angle is the minimum over both planes, so plane order doesn't matter.

New: kagan_angle (via scipy's D2 rotation group), MohoModel, SeismogenicZone, SEISMOGENIC_ZONES, SlabModel(grids, seismogenic_zones), SlabQuery.seismogenic_zone, and an optional moho_model keyword on CMTClassifier (from_faults/load pass the packaged grid). The ramps are np.interp calls on KAGAN_RAMP / CRUSTAL_DEPTH_DIFFERENCE_RAMP.

Removed:

  • The NSHM 2022 rule: the old signature of the module-level tectonic_type_probabilities, _is_interface_like and its constants, and the sigma keyword of CMTClassifier.tectonic_type_probabilities.
  • The NGA-SUB rule: nga_sub_tectonic_type(_probabilities), SlabZone, SlabZoneQuery and SlabModel.zone.

validation_small_magnitude/scripts/compare_sources.py passes sigma=0.0 and will need updating.

Behaviour

The workflow test events keep their old classifications:

Event Result
2003 Fiordland interface
2016 Kaikōura crustal
2017p795065 slab

On the 81 crustal validation events in simulation_validation (GeoNet CMT centroids and planes):

Rule Crustal Interface Slab
CatSep (CRUST1.0 Moho) 74 5 2
CatSep (no Moho) 75 5 1
NSHM 2022 74 3 4

Six misses are within 10 km vertically of a shallow Hikurangi Slab2 surface, and five have Kagan angles of 20–48° to interface slip. The Moho adds one more: 2020p198433, a 24 km centroid in an 11 km CRUST1.0 cell, drops from 0.88 crustal to 0.43 (slab 0.47). The Moho can only lower crustal scores.

Test plan

  • The rule's branches, including the mantle wedge and a Moho below the slab; seismogenic zone and Moho lookups; plane-order invariance; and the no-mechanism / outside-footprint paths
  • The np.interp/scipy simplification matches the previous implementation to 1e-15 over 200,000 random inputs (Kagan angles to 1e-12°)
  • Kagan angle identities and the ≤ 120° bound
  • Full suite: 862 passed; ruff, ty and npdlint clean

🤖 Generated with Claude Code

lispandfound and others added 2 commits October 27, 2026 14:16
Within the Slab2 footprint, the probability of a subduction interface
event is the product of an on-interface probability,
exp(-dz^2 / 2 sigma^2) with sigma = 10 km for the vertical distance to
the interface, a subduction slip probability, exp(-K^2 / 2 sigma^2) with
sigma = 30 degrees for the minimum Kagan angle to slip on the interface
in pure reverse or along the Pacific-Australia relative plate motion
(NUVEL-1A), and whether the centroid is no deeper than 70 km. The
remainder is upper plate above the interface and intraslab below it.
Fixed depths and missing mechanisms leave the corresponding factor at 1.

Adds kagan_angle, plate_motion_azimuth and subduction_slip_planes, and
CMTClassifier.haynie_tectonic_type(_probabilities).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CMTClassifier.tectonic_type(centroid, nodal_plane_1, nodal_plane_2), the
call gcmt-to-realisation makes, now classifies by the Haynie et al. rule.
The planes are optional (no focal mechanism) and a fixed_depth keyword
drops the on-interface factor. The Kagan angle is taken over both planes
so the result does not depend on their order.

Removes the NSHM 2022 rule (tectonic_type_probabilities' old signature,
_is_interface_like and its constants), the NGA-SUB rule
(nga_sub_tectonic_type(_probabilities), SlabZone, SlabZoneQuery,
SlabModel.zone) and the haynie_* names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lispandfound and others added 2 commits October 27, 2026 20:09
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the Gaussian interface likelihoods and plate-motion reference
slip with CatSep v1.0.0's ramp functions: the interface score is the
flex-zone score (within 15 km of Slab2), times a ramp from 1 at the
seismogenic zone depth to 0 15 km below, times a Kagan ramp (1 at 30
degrees to 0.25 at 53) against slip at the region's seismogenic rake.
Crustal is the remainder times a ramp from 20 km above to 20 km below
the slab, and intraslab takes the rest.

The CRUST1.0 Moho is not used: moho_depth defaults to infinity, so the
crust extends to the slab and the mantle-wedge branch never applies.
SlabQuery now carries its Slab2 region to look up the seismogenic zone
(Hayes et al. 2018; Puysegur 30 km, 90 degrees).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lispandfound lispandfound changed the title Classify tectonic type by Haynie et al. (2026); remove NSHM 2022 and NGA-SUB rules Classify tectonic type by CatSep (Haynie et al. 2026); remove NSHM 2022 and NGA-SUB rules Oct 27, 2026
lispandfound and others added 2 commits October 27, 2026 22:45
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Package CatSep's CRUST1.0 depth-to-Moho grid cropped to New Zealand
(crust1_moho_nz.npz, 2.7 kB) behind a MohoModel, and pass the Moho depth
at the centroid from CMTClassifier, completing CatSep's p_moho and
mantle-wedge flex terms.

Simplify: the ramps are np.interp calls on (xs, ps) constants instead of
a Ramp class; the Kagan angle uses scipy's D2 rotation group instead of
enumerating the double-couple symmetries by hand; SlabModel takes the
seismogenic zones again and SlabQuery carries its zone instead of a
region string looked up in a global; the redundant depth conditions on
the flex score are dropped. Scores match the previous implementation to
1e-15 over 200,000 random inputs and Kagan angles to 1e-12 degrees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lispandfound

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Claude finished @lispandfound's task in 4m 46s —— View job


Review complete

  • Gather context (PR body, diff, repo guidelines)
  • Read changed files in full
  • Review for correctness bugs, security issues, edge cases, regressions
  • Check for existing review comments to avoid duplicates (none found)
  • Post findings

No CLAUDE.md/REVIEW.md present in this repo to follow beyond the default correctness-focused review.

Findings: 0 blocking, 0 minor

I hand-traced tectonic_type_probabilities (source_modelling/focal_mechanism.py:512-589) against all 9 new parametrized cases in tests/test_focal_mechanism.py (including the mantle-wedge ramp and the "Moho below slab" discontinuity case), and every branch (flex, seismogenic, kagan, above_moho, crustal) reproduces the expected scores exactly by hand calculation — no arithmetic or branch-ordering bugs found.

Specifically checked and confirmed correct:

  • flex_top <= depth <= slab_depth + FLEX_ZONE_KM vs. the elif moho_depth <= depth < flex_top mantle-wedge branch don't overlap or leave gaps.
  • The above_moho cliff to 0.0 when moho_depth >= slab_depth (focal_mechanism.py:574-579) is intentional and exercised by test_tectonic_type_probabilities[48.0-40.0-None-45.0-...] — np.interp's clamp-to-boundary semantics make the degenerate xp=[m, m] case well-defined here, not a bug.
  • _slip_vector (new) matches the standard Aki & Richards eq. 4.88 slip-vector formula used alongside the pre-existing _plane_normal.
  • KAGAN_RAMP/CRUSTAL_DEPTH_DIFFERENCE_RAMP are unpacked into np.interp(x, xp, fp) in the correct order.
  • No remaining references to the removed nga_sub_*, SlabZone, _is_interface_like, or sigma APIs anywhere else in this repo (the one known external caller, validation_small_magnitude/scripts/compare_sources.py, lives outside this checkout and is already flagged by the author as needing a follow-up update).
  • CMTClassifier.tectonic_type_probabilities's new moho_model.depth(lat, lon) call happens even when the point is outside the slab footprint and the result is discarded by the early-return in the module-level function — harmless, not worth a line comment.

Didn't find anything to raise as an inline comment.

@lispandfound
lispandfound merged commit 7c0804a into main Oct 27, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant