Four tools for seismic data A developer built four open-source tools spanning a seismic data pipeline: torch-friction, a differentiable PyTorch rate-and-state friction layer that recovered a, b and Dc parameters with under 0.05% error after inversion; Basalt, a C++ frequency-wavenumber filter built on memory-mapped SEG-Y and AVX2/AVX-512 kernels that removed ground roll by −20.7 dB; fast-trigger, a C++23 recursive STA/LTA event detector with a NumPy-friendly Python API that needs roughly 2 dB less signal at a 1% false-alarm rate; and seismic-linter, a PyPI static analyser that flags temporal data leakage in ML code before training. The tools target two recurring problems in seismology ML: physics bolted on after the fact rather than inside the computational graph, and fragile evaluation from time-series leakage. Originally published at amansinghnp.com https://amansinghnp.com/writing/seismic-pytorch . Over 2025 and 2026 I built four small open-source tools that sit at different points of a seismic data pipeline. Each solves a different problem, so start with the one you need: | Tool | What it does | Reach for it if you | Code | |---|---|---|---| | torch-friction | Rate-and-state friction as a differentiable PyTorch layer, so friction parameters can be fitted by gradient descent | want to recover a , b and Dc from friction data, or put exact friction physics inside a larger network | GitHub https://github.com/AmanSinghNp/torch-friction | | Basalt | C++ frequency-wavenumber f-k filtering, built around memory-mapped SEG-Y, aligned memory, tiled FFTs and AVX2/AVX-512 kernels | need to remove ground roll from active-source shot gathers | GitHub https://github.com/AmanSinghNp/Basalt | | fast-trigger | Recursive STA/LTA event detection in C++23 with a NumPy-friendly Python API | need to flag events in single traces or batches of traces from Python | GitHub https://github.com/AmanSinghNp/fast-trigger | | seismic-linter | A static analyser on PyPI that flags temporal data leakage in ML code before training runs | train models on earthquake catalogues or other time series and want leaky code blocked at commit time | GitHub https://github.com/AmanSinghNp/seismic-linter | Each tool's section opens with a short entry: the clearest result, the main limitation and how it was tested. The physics, numerics and full test results follow. Every chart below was produced by running the published code or, where noted, a line-for-line NumPy port of it on the data listed in section 6. Some of the results are flattering and some are not. I've kept both. The earthquake-severity model that led to seismic-linter is on the Projects page as South Asia Earthquake Severity https://amansinghnp.com/projects earthquake . < 0.05% friction parameter error after inversion −20.7 dB ground roll removed by the f-k mute ≈ 2 dB less signal needed by x² STA/LTA at a 1% false-alarm rate Machine learning in seismology has moved fast. Deep networks now pick phases and detect events at scale Mousavi & Beroza, 2022 , and physics-informed neural networks PINNs put governing equations into the loss function Raissi et al., 2019; Karniadakis et al., 2021 . Two problems keep coming up anyway. The first is that the physics is often bolted on after the fact, instead of living inside the computational graph where gradients can reach it. The second is that evaluation is fragile: time-series leakage has inflated reported results across many fields Kapoor & Narayanan, 2023 , and earthquake forecasting has its own history of complex models that turn out no better than simple baselines Mignan & Broccardo, 2019, 2020 . The four tools line up against those two problems. torch-friction and Basalt handle the physics and signal-processing side. fast-trigger handles detection. seismic-linter guards the evaluation. torch-friction in brief torch-friction from PyPI in float64 on CPU. Laboratory rock friction isn't a constant. It depends on how fast the surfaces slide and on how long the contacts have had to age. Dieterich 1979 and Ruina 1983 captured this with a friction coefficient that depends on slip velocity V and a state variable θ , which you can read as the average lifetime of the asperity contacts: μ V, θ = μ0 + a ln V / V0 + b ln V0θ / Dc The a term is the direct effect : friction jumps immediately when velocity changes. The b term is the evolution effect : as the contact population turns over across a characteristic slip distance Dc , friction relaxes to a new steady state. At steady state, θ = Dc / V and μss = μ0 + a − b ln V/V0 . When a − b < 0 the fault is velocity-weakening : sliding faster makes it weaker, which is the precondition for earthquake nucleation Marone, 1998; Scholz, 1998 . Whether that weakening actually produces an earthquake depends on the surroundings. In the classic spring-slider model, a block dragged through a spring of stiffness k slides stably if k is above a critical stiffness kc = σ b − a / Dc , and stick-slips below it Ruina, 1983; Rice & Ruina, 1983 . Figure 1. The spring-slider model. The load point moves at a constant rate, stress builds in the spring while the block sticks, then drops when the block slips. With velocity-weakening friction and k < kc , this cycle repeats indefinitely: the laboratory analogue of the seismic cycle. The friction equation says nothing about how θ changes over time. There are two standard choices, and torch-friction implements both as law='aging' and law='slip' : | Law | dθ/dt | Behaviour | Source | |---|---|---|---| | Aging Dieterich | 1 − Vθ/Dc | State grows with time even at rest, so contacts heal while stationary | Dieterich 1979 | | Slip Ruina | − Vθ/Dc ln Vθ/Dc | State only evolves with slip; symmetric response to velocity steps up and down | Ruina 1983 | The standard lab test is a velocity step. Figure 2 runs one through FrictionLayer.forward trajectory with a = 0.015, b = 0.020 and Dc = 10 µm, stepping from 1 to 10 µm/s and back. Figure 2. Velocity step up at t = 2 s and back down at t = 8 s, computed with torch-friction Δt = 2 ms, RK4 . The jump of a ·ln10 ≈ 0.035 is the direct effect; the settling to a − b ·ln10 ≈ −0.012 below the starting level is the evolution effect. The two laws agree on the peak and the steady state but differ on the path between them. The two laws only differ in the transient, but the difference isn't small: up to Δμ = 0.012 about one second after the step up, which is a third of the whole direct effect. The asymmetry is easy to see from the equations. After a tenfold step up, Vθ/Dc = 10, so the aging law evolves at a rate of 1 − 10 = −9 while the slip law evolves at −10 ln 10 ≈ −23. The slip law weakens faster. After the step down Vθ/Dc = 0.1 it's reversed: 0.9 for aging against 0.23 for slip, so the aging law heals faster. Six seconds after the step down, the aging curve has recovered to μ = 0.586 while the slip curve is still at 0.575. Lab experiments with large velocity steps tend to favour the slip law Bhattacharya et al., 2015 , which is why the library keeps both. Each call to the layer advances θ by one classical fourth-order Runge-Kutta RK4 step, then evaluates the friction equation at the new state. The core is short: torch friction/layers.py python def rk4 step self, t, theta, v, dt, Dc : k1 = self.physics t, theta, v, Dc k2 = self.physics t + dt/2, theta + k1 dt/2, v, Dc k3 = self.physics t + dt/2, theta + k2 dt/2, v, Dc k4 = self.physics t + dt, theta + k3 dt, v, Dc return theta + dt/6.0 k1 + 2 k2 + 2 k3 + k4 The state equation is stiff when Dc is small relative to V ·Δt. Linearising the aging law about steady state gives an eigenvalue of −V/Dc, and RK4's stability interval on the negative real axis ends near −2.79 Hairer & Wanner, 1996 , so explicit integration needs Δt < 2.79 Dc/V. The layer warns once Δt goes past 0.5 Dc/Vmax, which leaves a margin of about 5.6× below the stability limit. That margin is sensible, because the accuracy limit arrives well before the stability limit does. To check the accuracy, Figure 3 relaxes the aging law from θ 0 = 10 s at constant V , where the exact solution is θ t = Dc/V + θ0 − Dc/V e