DASPy-toolbox 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DASPy_toolbox-1.0.0.dist-info/LICENSE.txt +1 -0
- DASPy_toolbox-1.0.0.dist-info/METADATA +85 -0
- DASPy_toolbox-1.0.0.dist-info/RECORD +49 -0
- DASPy_toolbox-1.0.0.dist-info/WHEEL +5 -0
- DASPy_toolbox-1.0.0.dist-info/entry_points.txt +2 -0
- DASPy_toolbox-1.0.0.dist-info/top_level.txt +1 -0
- daspy/__init__.py +4 -0
- daspy/advanced_tools/__init__.py +0 -0
- daspy/advanced_tools/channel.py +354 -0
- daspy/advanced_tools/decomposition.py +165 -0
- daspy/advanced_tools/denoising.py +276 -0
- daspy/advanced_tools/fdct.py +789 -0
- daspy/advanced_tools/strain2vel.py +245 -0
- daspy/basic_tools/__init__.py +0 -0
- daspy/basic_tools/filter.py +257 -0
- daspy/basic_tools/freqattributes.py +117 -0
- daspy/basic_tools/preprocessing.py +238 -0
- daspy/basic_tools/visualization.py +186 -0
- daspy/core/__init__.py +4 -0
- daspy/core/collection.py +279 -0
- daspy/core/dasdatetime.py +72 -0
- daspy/core/example.pkl +0 -0
- daspy/core/make_example.py +32 -0
- daspy/core/read.py +544 -0
- daspy/core/section.py +1319 -0
- daspy/core/write.py +282 -0
- daspy/seismic_detection/__init__.py +1 -0
- daspy/seismic_detection/calc_travel_time.py +23 -0
- daspy/seismic_detection/core.py +119 -0
- daspy/seismic_detection/detection.py +12 -0
- daspy/seismic_detection/gamma/__init__.py +13 -0
- daspy/seismic_detection/gamma/_base.py +549 -0
- daspy/seismic_detection/gamma/_bayesian_mixture.py +875 -0
- daspy/seismic_detection/gamma/_gaussian_mixture.py +866 -0
- daspy/seismic_detection/gamma/app.py +192 -0
- daspy/seismic_detection/gamma/seismic_ops.py +478 -0
- daspy/seismic_detection/gamma/utils.py +512 -0
- daspy/seismic_detection/location.py +266 -0
- daspy/seismic_detection/magnitude.py +43 -0
- daspy/seismic_detection/phase_picking.py +67 -0
- daspy/structure_imaging/__init__.py +0 -0
- daspy/structure_imaging/ambient_noise.py +4 -0
- daspy/structure_imaging/dispersion.py +27 -0
- daspy/structure_imaging/fault_zone.py +59 -0
- daspy/structure_imaging/inversion.py +6 -0
- daspy/traffic_monitoring/JamDetection.py +6 -0
- daspy/traffic_monitoring/SpeedMeasurement.py +6 -0
- daspy/traffic_monitoring/VehicleDetection.py +6 -0
- daspy/traffic_monitoring/__init__.py +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from json import dumps
|
|
3
|
+
from typing import Dict, List, Union
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from kafka import KafkaProducer
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from pyproj import Proj
|
|
11
|
+
|
|
12
|
+
from gamma.utils import association
|
|
13
|
+
|
|
14
|
+
# Kafak producer
|
|
15
|
+
use_kafka = False
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
print('Connecting to k8s kafka')
|
|
19
|
+
BROKER_URL = 'quakeflow-kafka-headless:9092'
|
|
20
|
+
# BROKER_URL = "34.83.137.139:9094"
|
|
21
|
+
producer = KafkaProducer(
|
|
22
|
+
bootstrap_servers=[BROKER_URL],
|
|
23
|
+
key_serializer=lambda x: dumps(x).encode('utf-8'),
|
|
24
|
+
value_serializer=lambda x: dumps(x).encode('utf-8'),
|
|
25
|
+
)
|
|
26
|
+
use_kafka = True
|
|
27
|
+
print('k8s kafka connection success!')
|
|
28
|
+
except BaseException:
|
|
29
|
+
print('k8s Kafka connection error')
|
|
30
|
+
try:
|
|
31
|
+
print('Connecting to local kafka')
|
|
32
|
+
producer = KafkaProducer(
|
|
33
|
+
bootstrap_servers=['localhost:9092'],
|
|
34
|
+
key_serializer=lambda x: dumps(x).encode('utf-8'),
|
|
35
|
+
value_serializer=lambda x: dumps(x).encode('utf-8'),
|
|
36
|
+
)
|
|
37
|
+
use_kafka = True
|
|
38
|
+
print('local kafka connection success!')
|
|
39
|
+
except BaseException:
|
|
40
|
+
print('local Kafka connection error')
|
|
41
|
+
print(f"Kafka status: {use_kafka}")
|
|
42
|
+
|
|
43
|
+
app = FastAPI()
|
|
44
|
+
|
|
45
|
+
PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
|
|
46
|
+
STATION_CSV = os.path.join(PROJECT_ROOT, "tests/stations_hawaii.csv")
|
|
47
|
+
# STATION_CSV = os.path.join(PROJECT_ROOT, "tests/stations.csv") ## ridgecrest
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def default_config(config):
|
|
51
|
+
if "degree2km" not in config:
|
|
52
|
+
config["degree2km"] = 111.195
|
|
53
|
+
if "use_amplitude" not in config:
|
|
54
|
+
config["use_amplitude"] = True
|
|
55
|
+
if "use_dbscan" not in config:
|
|
56
|
+
config["use_dbscan"] = True
|
|
57
|
+
if "dbscan_eps" not in config:
|
|
58
|
+
config["dbscan_eps"] = 30.0
|
|
59
|
+
if "dbscan_min_samples" not in config:
|
|
60
|
+
config["dbscan_min_samples"] = 3
|
|
61
|
+
if "method" not in config:
|
|
62
|
+
config["method"] = "BGMM"
|
|
63
|
+
if "oversample_factor" not in config:
|
|
64
|
+
config["oversample_factor"] = 5
|
|
65
|
+
if "min_picks_per_eq" not in config:
|
|
66
|
+
config["min_picks_per_eq"] = 10
|
|
67
|
+
if "max_sigma11" not in config:
|
|
68
|
+
config["max_sigma11"] = 2.0
|
|
69
|
+
if "max_sigma22" not in config:
|
|
70
|
+
config["max_sigma22"] = 1.0
|
|
71
|
+
if "max_sigma12" not in config:
|
|
72
|
+
config["max_sigma12"] = 1.0
|
|
73
|
+
if "dims" not in config:
|
|
74
|
+
config["dims"] = ["x(km)", "y(km)", "z(km)"]
|
|
75
|
+
return config
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## set config
|
|
79
|
+
config = {'xlim_degree': [-156.32, -154.32], 'ylim_degree': [18.39, 20.39], "z(km)": [0, 41]} ## hawaii
|
|
80
|
+
# config = {'xlim_degree': [-118.004, -117.004], 'ylim_degree': [35.205, 36.205], "z(km)": [0, 41]} ## ridgecrest
|
|
81
|
+
|
|
82
|
+
config = default_config(config)
|
|
83
|
+
config["center"] = [np.mean(config["xlim_degree"]), np.mean(config["ylim_degree"])]
|
|
84
|
+
config["x(km)"] = (np.array(config["xlim_degree"]) - config["center"][0]) * config["degree2km"]
|
|
85
|
+
config["y(km)"] = (np.array(config["ylim_degree"]) - config["center"][1]) * config["degree2km"]
|
|
86
|
+
config["bfgs_bounds"] = [list(config[x]) for x in config["dims"]] + [[None, None]]
|
|
87
|
+
|
|
88
|
+
for k, v in config.items():
|
|
89
|
+
print(f"{k}: {v}")
|
|
90
|
+
|
|
91
|
+
## read stations
|
|
92
|
+
stations = pd.read_csv(STATION_CSV, delimiter="\t")
|
|
93
|
+
stations = stations.rename(columns={"station": "id"})
|
|
94
|
+
stations["x(km)"] = stations["longitude"].apply(lambda x: (x - config["center"][0]) * config["degree2km"])
|
|
95
|
+
stations["y(km)"] = stations["latitude"].apply(lambda x: (x - config["center"][1]) * config["degree2km"])
|
|
96
|
+
stations["z(km)"] = stations["elevation(m)"].apply(lambda x: -x / 1e3)
|
|
97
|
+
|
|
98
|
+
print(stations)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Data(BaseModel):
|
|
102
|
+
picks: List[Dict[str, Union[float, str]]]
|
|
103
|
+
stations: List[Dict[str, Union[float, str]]]
|
|
104
|
+
config: Dict[str, Union[List[float], List[int], List[str], float, int, str]]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Pick(BaseModel):
|
|
108
|
+
picks: List[Dict[str, Union[float, str]]]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def run_gamma(picks, config, stations):
|
|
112
|
+
|
|
113
|
+
proj = Proj(f"+proj=sterea +lon_0={config['center'][0]} +lat_0={config['center'][1]} +units=km")
|
|
114
|
+
|
|
115
|
+
stations[["x(km)", "y(km)"]] = stations.apply(lambda x: pd.Series(proj(longitude=x.longitude, latitude=x.latitude)), axis=1)
|
|
116
|
+
stations["z(km)"] = stations["elevation(m)"].apply(lambda x: -x/1e3)
|
|
117
|
+
|
|
118
|
+
catalogs, assignments = association(picks, stations, config, 0, config["method"])
|
|
119
|
+
|
|
120
|
+
catalogs = pd.DataFrame(catalogs, columns=["time"]+config["dims"]+["magnitude", "sigma_time", "sigma_amp", "cov_time_amp", "event_index", "gamma_score"])
|
|
121
|
+
catalogs[["longitude","latitude"]] = catalogs.apply(lambda x: pd.Series(proj(longitude=x["x(km)"], latitude=x["y(km)"], inverse=True)), axis=1)
|
|
122
|
+
catalogs["depth(m)"] = catalogs["z(km)"].apply(lambda x: x*1e3)
|
|
123
|
+
|
|
124
|
+
assignments = pd.DataFrame(assignments, columns=["pick_index", "event_index", "gamma_score"])
|
|
125
|
+
picks_gamma = picks.join(assignments.set_index("pick_index")).fillna(-1).astype({'event_index': int})
|
|
126
|
+
|
|
127
|
+
return catalogs, picks_gamma
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.post('/predict_stream')
|
|
131
|
+
def predict(data: Pick):
|
|
132
|
+
|
|
133
|
+
picks = pd.DataFrame(data.picks)
|
|
134
|
+
if len(picks) == 0:
|
|
135
|
+
return {"catalog": [], "picks": []}
|
|
136
|
+
|
|
137
|
+
catalogs, picks_gamma = run_gamma(data, config, stations)
|
|
138
|
+
|
|
139
|
+
if use_kafka:
|
|
140
|
+
print("Push events to kafka...")
|
|
141
|
+
for event in catalogs.to_dict(orient="records"):
|
|
142
|
+
producer.send('gmma_events', key=event["time"], value=event)
|
|
143
|
+
|
|
144
|
+
return {"catalog": catalogs.to_dict(orient="records"), "picks": picks_gamma.to_dict(orient="records")}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@app.post('/predict')
|
|
148
|
+
def predict(data: Data):
|
|
149
|
+
|
|
150
|
+
picks = pd.DataFrame(data.picks)
|
|
151
|
+
if len(picks) == 0:
|
|
152
|
+
return {"catalog": [], "picks": []}
|
|
153
|
+
|
|
154
|
+
stations = pd.DataFrame(data.stations)
|
|
155
|
+
if len(stations) == 0:
|
|
156
|
+
return {"catalog": [], "picks": []}
|
|
157
|
+
|
|
158
|
+
assert "latitude" in stations
|
|
159
|
+
assert "longitude" in stations
|
|
160
|
+
assert "elevation(m)" in stations
|
|
161
|
+
|
|
162
|
+
config = data.config
|
|
163
|
+
config = default_config(config)
|
|
164
|
+
|
|
165
|
+
if "xlim_degree" not in config:
|
|
166
|
+
config["xlim_degree"] = (stations["longitude"].min(), stations["longitude"].max())
|
|
167
|
+
if "ylim_degree" not in config:
|
|
168
|
+
config["ylim_degree"] = (stations["latitude"].min(), stations["latitude"].max())
|
|
169
|
+
if "center" not in config:
|
|
170
|
+
config["center"] = [np.mean(config["xlim_degree"]), np.mean(config["ylim_degree"])]
|
|
171
|
+
if "x(km)" not in config:
|
|
172
|
+
config["x(km)"] = (np.array(config["xlim_degree"]) - config["center"][0]) * config["degree2km"] * np.cos(np.deg2rad(config["center"][1]))
|
|
173
|
+
if "y(km)" not in config:
|
|
174
|
+
config["y(km)"] = (np.array(config["ylim_degree"]) - config["center"][1]) * config["degree2km"]
|
|
175
|
+
if "z(km)" not in config:
|
|
176
|
+
config["z(km)"] = (0, 41)
|
|
177
|
+
if "bfgs_bounds" not in config:
|
|
178
|
+
config["bfgs_bounds"] = [list(config[x]) for x in config["dims"]] + [[None, None]]
|
|
179
|
+
|
|
180
|
+
catalogs, picks_gamma = run_gamma(picks, config, stations)
|
|
181
|
+
|
|
182
|
+
if use_kafka:
|
|
183
|
+
print("Push events to kafka...")
|
|
184
|
+
for event in catalogs.to_dict(orient="records"):
|
|
185
|
+
producer.send('gamma_events', key=event["time"], value=event)
|
|
186
|
+
|
|
187
|
+
return {"catalog": catalogs.to_dict(orient="records"), "picks": picks_gamma.to_dict(orient="records")}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@app.get("/healthz")
|
|
191
|
+
def healthz():
|
|
192
|
+
return {"status": "ok"}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import numpy as np
|
|
3
|
+
import scipy.optimize
|
|
4
|
+
import shelve
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from numba import njit
|
|
7
|
+
from numba.typed import List
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
###################################### Eikonal Solver ######################################
|
|
11
|
+
# |\nabla u| = f
|
|
12
|
+
# ((u - a1)^+)^2 + ((u - a2)^+)^2 + ((u - a3)^+)^2 = f^2 h^2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@njit
|
|
16
|
+
def calculate_unique_solution(a, b, f, h):
|
|
17
|
+
d = abs(a - b)
|
|
18
|
+
if d >= f * h:
|
|
19
|
+
return min([a, b]) + f * h
|
|
20
|
+
else:
|
|
21
|
+
return (a + b + np.sqrt(2 * f * f * h * h - (a - b) ** 2)) / 2
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@njit
|
|
25
|
+
def sweeping_over_I_J_K(u, I, J, f, h):
|
|
26
|
+
m = len(I)
|
|
27
|
+
n = len(J)
|
|
28
|
+
|
|
29
|
+
# for i, j in itertools.product(I, J):
|
|
30
|
+
for i in I:
|
|
31
|
+
for j in J:
|
|
32
|
+
if i == 0:
|
|
33
|
+
uxmin = u[i + 1, j]
|
|
34
|
+
elif i == m - 1:
|
|
35
|
+
uxmin = u[i - 1, j]
|
|
36
|
+
else:
|
|
37
|
+
uxmin = min([u[i - 1, j], u[i + 1, j]])
|
|
38
|
+
|
|
39
|
+
if j == 0:
|
|
40
|
+
uymin = u[i, j + 1]
|
|
41
|
+
elif j == n - 1:
|
|
42
|
+
uymin = u[i, j - 1]
|
|
43
|
+
else:
|
|
44
|
+
uymin = min([u[i, j - 1], u[i, j + 1]])
|
|
45
|
+
|
|
46
|
+
u_new = calculate_unique_solution(uxmin, uymin, f[i, j], h)
|
|
47
|
+
|
|
48
|
+
u[i, j] = min([u_new, u[i, j]])
|
|
49
|
+
|
|
50
|
+
return u
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@njit
|
|
54
|
+
def sweeping(u, v, h):
|
|
55
|
+
f = 1.0 / v ## slowness
|
|
56
|
+
|
|
57
|
+
m, n = u.shape
|
|
58
|
+
# I = list(range(m))
|
|
59
|
+
# I = List()
|
|
60
|
+
# [I.append(i) for i in range(m)]
|
|
61
|
+
I = np.arange(m)
|
|
62
|
+
iI = I[::-1]
|
|
63
|
+
# J = list(range(n))
|
|
64
|
+
# J = List()
|
|
65
|
+
# [J.append(j) for j in range(n)]
|
|
66
|
+
J = np.arange(n)
|
|
67
|
+
iJ = J[::-1]
|
|
68
|
+
|
|
69
|
+
u = sweeping_over_I_J_K(u, I, J, f, h)
|
|
70
|
+
u = sweeping_over_I_J_K(u, iI, J, f, h)
|
|
71
|
+
u = sweeping_over_I_J_K(u, iI, iJ, f, h)
|
|
72
|
+
u = sweeping_over_I_J_K(u, I, iJ, f, h)
|
|
73
|
+
|
|
74
|
+
return u
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def eikonal_solve(u, f, h):
|
|
78
|
+
print("Eikonal Solver: ")
|
|
79
|
+
t0 = time.time()
|
|
80
|
+
for i in range(50):
|
|
81
|
+
u_old = np.copy(u)
|
|
82
|
+
u = sweeping(u, f, h)
|
|
83
|
+
|
|
84
|
+
err = np.max(np.abs(u - u_old))
|
|
85
|
+
print(f"Iter {i}, error = {err:.3f}")
|
|
86
|
+
if err < 1e-6:
|
|
87
|
+
break
|
|
88
|
+
print(f"Time: {time.time() - t0:.3f}")
|
|
89
|
+
return u
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
###################################### Traveltime based on Eikonal Timetable ######################################
|
|
93
|
+
@njit
|
|
94
|
+
def _get_index(ir, iz, nr, nz, order="C"):
|
|
95
|
+
if order == "C":
|
|
96
|
+
return ir * nz + iz
|
|
97
|
+
elif order == "F":
|
|
98
|
+
return iz * nr + ir
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError("order must be either C or F")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_get_index():
|
|
104
|
+
vr, vz = np.meshgrid(np.arange(10), np.arange(20), indexing="ij")
|
|
105
|
+
vr = vr.flatten()
|
|
106
|
+
vz = vz.flatten()
|
|
107
|
+
nr = 10
|
|
108
|
+
nz = 20
|
|
109
|
+
for ir in range(nr):
|
|
110
|
+
for iz in range(nz):
|
|
111
|
+
assert vr[_get_index(ir, iz, nr, nz)] == ir
|
|
112
|
+
assert vz[_get_index(ir, iz, nr, nz)] == iz
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@njit
|
|
116
|
+
def _interp(time_table, r, z, rgrid0, zgrid0, nr, nz, h):
|
|
117
|
+
ir0 = np.floor((r - rgrid0) / h).clip(0, nr - 2).astype(np.int64)
|
|
118
|
+
iz0 = np.floor((z - zgrid0) / h).clip(0, nz - 2).astype(np.int64)
|
|
119
|
+
ir1 = ir0 + 1
|
|
120
|
+
iz1 = iz0 + 1
|
|
121
|
+
|
|
122
|
+
## https://en.wikipedia.org/wiki/Bilinear_interpolation
|
|
123
|
+
x1 = ir0 * h + rgrid0
|
|
124
|
+
x2 = ir1 * h + rgrid0
|
|
125
|
+
y1 = iz0 * h + zgrid0
|
|
126
|
+
y2 = iz1 * h + zgrid0
|
|
127
|
+
|
|
128
|
+
Q11 = time_table[_get_index(ir0, iz0, nr, nz)]
|
|
129
|
+
Q12 = time_table[_get_index(ir0, iz1, nr, nz)]
|
|
130
|
+
Q21 = time_table[_get_index(ir1, iz0, nr, nz)]
|
|
131
|
+
Q22 = time_table[_get_index(ir1, iz1, nr, nz)]
|
|
132
|
+
|
|
133
|
+
t = (
|
|
134
|
+
1
|
|
135
|
+
/ (x2 - x1)
|
|
136
|
+
/ (y2 - y1)
|
|
137
|
+
* (
|
|
138
|
+
Q11 * (x2 - r) * (y2 - z)
|
|
139
|
+
+ Q21 * (r - x1) * (y2 - z)
|
|
140
|
+
+ Q12 * (x2 - r) * (z - y1)
|
|
141
|
+
+ Q22 * (r - x1) * (z - y1)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return t
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def traveltime(event_loc, station_loc, phase_type, eikonal):
|
|
149
|
+
r = np.linalg.norm(event_loc[:, :2] - station_loc[:, :2], axis=-1, keepdims=False)
|
|
150
|
+
z = event_loc[:, 2] - station_loc[:, 2]
|
|
151
|
+
|
|
152
|
+
rgrid0 = eikonal["rgrid"][0]
|
|
153
|
+
zgrid0 = eikonal["zgrid"][0]
|
|
154
|
+
nr = eikonal["nr"]
|
|
155
|
+
nz = eikonal["nz"]
|
|
156
|
+
h = eikonal["h"]
|
|
157
|
+
|
|
158
|
+
if isinstance(phase_type, list):
|
|
159
|
+
phase_type = np.array(phase_type)
|
|
160
|
+
p_index = phase_type == "p"
|
|
161
|
+
s_index = phase_type == "s"
|
|
162
|
+
tt = np.zeros(len(phase_type), dtype=np.float32)
|
|
163
|
+
tt[phase_type == "p"] = _interp(eikonal["up"], r[p_index], z[p_index], rgrid0, zgrid0, nr, nz, h)
|
|
164
|
+
tt[phase_type == "s"] = _interp(eikonal["us"], r[s_index], z[s_index], rgrid0, zgrid0, nr, nz, h)
|
|
165
|
+
tt = tt[:, np.newaxis]
|
|
166
|
+
|
|
167
|
+
return tt
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def grad_traveltime(event_loc, station_loc, phase_type, eikonal):
|
|
171
|
+
r = np.linalg.norm(event_loc[:, :2] - station_loc[:, :2], axis=-1, keepdims=False)
|
|
172
|
+
z = event_loc[:, 2] - station_loc[:, 2]
|
|
173
|
+
|
|
174
|
+
rgrid0 = eikonal["rgrid"][0]
|
|
175
|
+
zgrid0 = eikonal["zgrid"][0]
|
|
176
|
+
nr = eikonal["nr"]
|
|
177
|
+
nz = eikonal["nz"]
|
|
178
|
+
h = eikonal["h"]
|
|
179
|
+
|
|
180
|
+
if isinstance(phase_type, list):
|
|
181
|
+
phase_type = np.array(phase_type)
|
|
182
|
+
p_index = phase_type == "p"
|
|
183
|
+
s_index = phase_type == "s"
|
|
184
|
+
dt_dr = np.zeros(len(phase_type))
|
|
185
|
+
dt_dz = np.zeros(len(phase_type))
|
|
186
|
+
dt_dr[p_index] = _interp(eikonal["grad_up"][0], r[p_index], z[p_index], rgrid0, zgrid0, nr, nz, h)
|
|
187
|
+
dt_dr[s_index] = _interp(eikonal["grad_us"][0], r[s_index], z[s_index], rgrid0, zgrid0, nr, nz, h)
|
|
188
|
+
dt_dz[p_index] = _interp(eikonal["grad_up"][1], r[p_index], z[p_index], rgrid0, zgrid0, nr, nz, h)
|
|
189
|
+
dt_dz[s_index] = _interp(eikonal["grad_us"][1], r[s_index], z[s_index], rgrid0, zgrid0, nr, nz, h)
|
|
190
|
+
|
|
191
|
+
dr_dxy = (event_loc[:, :2] - station_loc[:, :2]) / (r[:, np.newaxis] + 1e-6)
|
|
192
|
+
dt_dxy = dt_dr[:, np.newaxis] * dr_dxy
|
|
193
|
+
|
|
194
|
+
grad = np.column_stack((dt_dxy, dt_dz[:, np.newaxis]))
|
|
195
|
+
|
|
196
|
+
return grad
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
############################################# Seismic Ops for GaMMA #####################################################################
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def calc_time(event_loc, station_loc, phase_type, vel={"p": 6.0, "s": 6.0 / 1.75}, eikonal=None, **kwargs):
|
|
203
|
+
ev_loc = event_loc[:, :-1]
|
|
204
|
+
ev_t = event_loc[:, -1:]
|
|
205
|
+
|
|
206
|
+
if eikonal is None:
|
|
207
|
+
v = np.array([vel[x] for x in phase_type])[:, np.newaxis]
|
|
208
|
+
tt = np.linalg.norm(ev_loc - station_loc, axis=-1, keepdims=True) / v + ev_t
|
|
209
|
+
else:
|
|
210
|
+
tt = traveltime(event_loc, station_loc, phase_type, eikonal) + ev_t
|
|
211
|
+
return tt
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def calc_mag(data, event_loc, station_loc, weight, min=-2, max=8):
|
|
215
|
+
dist = np.linalg.norm(event_loc[:, :-1] - station_loc, axis=-1, keepdims=True)
|
|
216
|
+
# mag_ = ( data - 2.48 + 2.76 * np.log10(dist) )
|
|
217
|
+
## Picozzi et al. (2018) A rapid response magnitude scale...
|
|
218
|
+
c0, c1, c2, c3 = 1.08, 0.93, -0.015, -1.68
|
|
219
|
+
mag_ = (data - c0 - c3 * np.log10(np.maximum(dist, 0.1))) / c1 + 3.5
|
|
220
|
+
## Atkinson, G. M. (2015). Ground-Motion Prediction Equation...
|
|
221
|
+
# c0, c1, c2, c3, c4 = (-4.151, 1.762, -0.09509, -1.669, -0.0006)
|
|
222
|
+
# mag_ = (data - c0 - c3*np.log10(dist))/c1
|
|
223
|
+
# mag = np.sum(mag_ * weight) / (np.sum(weight)+1e-6)
|
|
224
|
+
# (Watanabe, 1971) https://www.jstage.jst.go.jp/article/zisin1948/24/3/24_3_189/_pdf/-char/ja
|
|
225
|
+
# mag_ = 1.0/0.85 * (data + 1.73 * np.log10(np.maximum(dist, 0.1)) + 2.50)
|
|
226
|
+
mu = np.sum(mag_ * weight) / (np.sum(weight) + 1e-6)
|
|
227
|
+
std = np.sqrt(np.sum((mag_ - mu) ** 2 * weight) / (np.sum(weight) + 1e-12))
|
|
228
|
+
mask = np.abs(mag_ - mu) <= 2 * std
|
|
229
|
+
mag = np.sum(mag_[mask] * weight[mask]) / (np.sum(weight[mask]) + 1e-6)
|
|
230
|
+
mag = np.clip(mag, min, max)
|
|
231
|
+
return mag
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def calc_amp(mag, event_loc, station_loc):
|
|
235
|
+
dist = np.linalg.norm(event_loc[:, :-1] - station_loc, axis=-1, keepdims=True)
|
|
236
|
+
# logA = mag + 2.48 - 2.76 * np.log10(dist)
|
|
237
|
+
## Picozzi et al. (2018) A rapid response magnitude scale...
|
|
238
|
+
c0, c1, c2, c3 = 1.08, 0.93, -0.015, -1.68
|
|
239
|
+
logA = c0 + c1 * (mag - 3.5) + c3 * np.log10(np.maximum(dist, 0.1))
|
|
240
|
+
## Atkinson, G. M. (2015). Ground-Motion Prediction Equation...
|
|
241
|
+
# c0, c1, c2, c3, c4 = (-4.151, 1.762, -0.09509, -1.669, -0.0006)
|
|
242
|
+
# logA = c0 + c1*mag + c3*np.log10(dist)
|
|
243
|
+
# (Watanabe, 1971) https://www.jstage.jst.go.jp/article/zisin1948/24/3/24_3_189/_pdf/-char/ja
|
|
244
|
+
# logA = 0.85 * mag - 2.50 - 1.73 * np.log10(np.maximum(dist, 0.1))
|
|
245
|
+
return logA
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
################################################ Earthquake Location ################################################
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def huber_loss_grad(
|
|
252
|
+
event_loc, phase_time, phase_type, station_loc, weight, vel={"p": 6.0, "s": 6.0 / 1.75}, sigma=1, eikonal=None
|
|
253
|
+
):
|
|
254
|
+
event_loc = event_loc[np.newaxis, :]
|
|
255
|
+
predict_time = calc_time(event_loc, station_loc, phase_type, vel, eikonal)
|
|
256
|
+
t_diff = predict_time - phase_time
|
|
257
|
+
|
|
258
|
+
l1 = np.squeeze((np.abs(t_diff) > sigma))
|
|
259
|
+
l2 = np.squeeze((np.abs(t_diff) <= sigma))
|
|
260
|
+
|
|
261
|
+
# loss
|
|
262
|
+
loss = np.sum((sigma * np.abs(t_diff[l1]) - 0.5 * sigma**2) * weight[l1]) + np.sum(
|
|
263
|
+
0.5 * t_diff[l2] ** 2 * weight[l2]
|
|
264
|
+
)
|
|
265
|
+
J = np.zeros([phase_time.shape[0], event_loc.shape[1]])
|
|
266
|
+
|
|
267
|
+
# gradient
|
|
268
|
+
if eikonal is None:
|
|
269
|
+
v = np.array([vel[p] for p in phase_type])[:, np.newaxis]
|
|
270
|
+
dist = np.linalg.norm(event_loc[:, :-1] - station_loc, axis=-1, keepdims=True)
|
|
271
|
+
J[:, :-1] = (event_loc[:, :-1] - station_loc) / (dist + 1e-6) / v
|
|
272
|
+
else:
|
|
273
|
+
grad = grad_traveltime(event_loc, station_loc, phase_type, eikonal)
|
|
274
|
+
J[:, :-1] = grad
|
|
275
|
+
J[:, -1] = 1
|
|
276
|
+
|
|
277
|
+
J_ = np.sum(sigma * np.sign(t_diff[l1]) * J[l1] * weight[l1], axis=0, keepdims=True) + np.sum(
|
|
278
|
+
t_diff[l2] * J[l2] * weight[l2], axis=0, keepdims=True
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return loss, J_
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def calc_loc(
|
|
285
|
+
phase_time,
|
|
286
|
+
phase_type,
|
|
287
|
+
station_loc,
|
|
288
|
+
weight,
|
|
289
|
+
event_loc0,
|
|
290
|
+
eikonal=None,
|
|
291
|
+
vel={"p": 6.0, "s": 6.0 / 1.75},
|
|
292
|
+
bounds=None,
|
|
293
|
+
max_iter=100,
|
|
294
|
+
convergence=1e-6,
|
|
295
|
+
):
|
|
296
|
+
|
|
297
|
+
opt = scipy.optimize.minimize(
|
|
298
|
+
huber_loss_grad,
|
|
299
|
+
np.squeeze(event_loc0),
|
|
300
|
+
method="L-BFGS-B",
|
|
301
|
+
jac=True,
|
|
302
|
+
args=(phase_time, phase_type, station_loc, weight, vel, 1, eikonal),
|
|
303
|
+
bounds=bounds,
|
|
304
|
+
options={"maxiter": max_iter, "gtol": convergence, "iprint": -1},
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return opt.x[np.newaxis, :], opt.fun
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def initialize_eikonal(config):
|
|
312
|
+
path = Path("./eikonal")
|
|
313
|
+
path.mkdir(exist_ok=True)
|
|
314
|
+
rlim = [0, np.sqrt((config["xlim"][1] - config["xlim"][0]) ** 2 + (config["ylim"][1] - config["ylim"][0]) ** 2)]
|
|
315
|
+
zlim = config["zlim"]
|
|
316
|
+
h = config["h"]
|
|
317
|
+
|
|
318
|
+
filename = f"timetable_{rlim[0]:.0f}_{rlim[1]:.0f}_{zlim[0]:.0f}_{zlim[1]:.0f}_{h:.3f}"
|
|
319
|
+
if (path / (filename + ".dir")).is_file():
|
|
320
|
+
print("Loading precomputed timetable...")
|
|
321
|
+
with shelve.open(str(path / filename)) as db:
|
|
322
|
+
up = db["up"]
|
|
323
|
+
us = db["us"]
|
|
324
|
+
grad_up = db["grad_up"]
|
|
325
|
+
grad_us = db["grad_us"]
|
|
326
|
+
rgrid = db["rgrid"]
|
|
327
|
+
zgrid = db["zgrid"]
|
|
328
|
+
nr = db["nr"]
|
|
329
|
+
nz = db["nz"]
|
|
330
|
+
h = db["h"]
|
|
331
|
+
else:
|
|
332
|
+
edge_grids = 0
|
|
333
|
+
|
|
334
|
+
rgrid = np.arange(rlim[0] - edge_grids * h, rlim[1], h)
|
|
335
|
+
zgrid = np.arange(zlim[0] - edge_grids * h, zlim[1], h)
|
|
336
|
+
nr, nz = len(rgrid), len(zgrid)
|
|
337
|
+
|
|
338
|
+
vel = config["vel"]
|
|
339
|
+
zz, vp, vs = vel["z"], vel["p"], vel["s"]
|
|
340
|
+
vp1d = np.interp(zgrid, zz, vp)
|
|
341
|
+
vs1d = np.interp(zgrid, zz, vs)
|
|
342
|
+
vp = np.ones((nr, nz)) * vp1d
|
|
343
|
+
vs = np.ones((nr, nz)) * vs1d
|
|
344
|
+
|
|
345
|
+
up = 1000.0 * np.ones((nr, nz))
|
|
346
|
+
up[edge_grids, edge_grids] = 0.0
|
|
347
|
+
up = eikonal_solve(up, vp, h)
|
|
348
|
+
|
|
349
|
+
grad_up = np.gradient(up, h)
|
|
350
|
+
|
|
351
|
+
us = 1000.0 * np.ones((nr, nz))
|
|
352
|
+
us[edge_grids, edge_grids] = 0.0
|
|
353
|
+
us = eikonal_solve(us, vs, h)
|
|
354
|
+
|
|
355
|
+
grad_us = np.gradient(us, h)
|
|
356
|
+
|
|
357
|
+
with shelve.open(str(path / filename)) as db:
|
|
358
|
+
db["up"] = up
|
|
359
|
+
db["us"] = us
|
|
360
|
+
db["grad_up"] = grad_up
|
|
361
|
+
db["grad_us"] = grad_us
|
|
362
|
+
db["rgrid"] = rgrid
|
|
363
|
+
db["zgrid"] = zgrid
|
|
364
|
+
db["nr"] = nr
|
|
365
|
+
db["nz"] = nz
|
|
366
|
+
db["h"] = h
|
|
367
|
+
|
|
368
|
+
up = up.flatten()
|
|
369
|
+
us = us.flatten()
|
|
370
|
+
grad_up = np.array([grad_up[0].flatten(), grad_up[1].flatten()])
|
|
371
|
+
grad_us = np.array([grad_us[0].flatten(), grad_us[1].flatten()])
|
|
372
|
+
config.update(
|
|
373
|
+
{
|
|
374
|
+
"up": up,
|
|
375
|
+
"us": us,
|
|
376
|
+
"grad_up": grad_up,
|
|
377
|
+
"grad_us": grad_us,
|
|
378
|
+
"rgrid": rgrid,
|
|
379
|
+
"zgrid": zgrid,
|
|
380
|
+
"nr": nr,
|
|
381
|
+
"nz": nz,
|
|
382
|
+
"h": h,
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
return config
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def initialize_centers(X, phase_type, centers_init, station_locs, random_state):
|
|
390
|
+
n_samples, n_features = X.shape
|
|
391
|
+
n_components, _ = centers_init.shape
|
|
392
|
+
centers = centers_init.copy()
|
|
393
|
+
|
|
394
|
+
means = np.zeros([n_components, n_samples, n_features])
|
|
395
|
+
for i in range(n_components):
|
|
396
|
+
if n_features == 1: # (time,)
|
|
397
|
+
means[i, :, :] = calc_time(centers_init[i : i + 1, :], station_locs, phase_type)
|
|
398
|
+
elif n_features == 2: # (time, amp)
|
|
399
|
+
means[i, :, 0:1] = calc_time(centers_init[i : i + 1, :-1], station_locs, phase_type)
|
|
400
|
+
means[i, :, 1:2] = X[:, 1:2]
|
|
401
|
+
# means[i, :, 1:2] = calc_amp(self.centers_init[i, -1:], self.centers_init[i:i+1, :-1], self.station_locs)
|
|
402
|
+
else:
|
|
403
|
+
raise ValueError(f"n_features = {n_features} > 2!")
|
|
404
|
+
|
|
405
|
+
## performance is not good
|
|
406
|
+
# resp = np.zeros((n_samples, self.n_components))
|
|
407
|
+
# dist = np.sum(np.abs(means - X), axis=-1).T # (n_components, n_samples, n_features) -> (n_samples, n_components)
|
|
408
|
+
# resp[np.arange(n_samples), np.argmax(dist, axis=1)] = 1.0
|
|
409
|
+
|
|
410
|
+
## performance is ok
|
|
411
|
+
# sigma = np.array([1.0,1.0])
|
|
412
|
+
# prob = np.sum(1.0/sigma * np.exp( - (means - X) ** 2 / (2 * sigma**2)), axis=-1).T # (n_components, n_samples, n_features) -> (n_samples, n_components)
|
|
413
|
+
# prob_sum = np.sum(prob, axis=1, keepdims=True)
|
|
414
|
+
# prob_sum[prob_sum == 0] = 1.0
|
|
415
|
+
# resp = prob / prob_sum
|
|
416
|
+
|
|
417
|
+
dist = np.linalg.norm(means - X, axis=-1).T # (n_components, n_samples, n_features) -> (n_samples, n_components)
|
|
418
|
+
resp = np.exp(-dist)
|
|
419
|
+
resp_sum = resp.sum(axis=1, keepdims=True)
|
|
420
|
+
resp_sum[resp_sum == 0] = 1.0
|
|
421
|
+
resp = resp / resp_sum
|
|
422
|
+
|
|
423
|
+
# dist = np.linalg.norm(means - X, axis=-1) # (n_components, n_samples, n_features) -> (n_components, n_samples)
|
|
424
|
+
# resp = np.exp(-dist/np.median(dist, axis=0, keepdims=True)).T
|
|
425
|
+
# resp /= np.sum(resp, axis=1, keepdims=True) # (n_components, n_samples)
|
|
426
|
+
|
|
427
|
+
if n_features == 2:
|
|
428
|
+
for i in range(n_components):
|
|
429
|
+
centers[i, -1:] = calc_mag(X[:, 1:2], centers_init[i : i + 1, :-1], station_locs, resp[:, i : i + 1])
|
|
430
|
+
|
|
431
|
+
return resp, centers, means
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
#########################################################################################################################
|
|
435
|
+
## L2 norm
|
|
436
|
+
def diff_and_grad(vars, data, station_locs, phase_type, vel={"p": 6.0, "s": 6.0 / 1.75}):
|
|
437
|
+
"""
|
|
438
|
+
data: (n_sample, t)
|
|
439
|
+
"""
|
|
440
|
+
v = np.array([vel[p] for p in phase_type])[:, np.newaxis]
|
|
441
|
+
# loc, t = vars[:,:-1], vars[:,-1:]
|
|
442
|
+
dist = np.sqrt(np.sum((station_locs - vars[:, :-1]) ** 2, axis=1, keepdims=True))
|
|
443
|
+
y = dist / v - (data - vars[:, -1:])
|
|
444
|
+
J = np.zeros([data.shape[0], vars.shape[1]])
|
|
445
|
+
J[:, :-1] = (vars[:, :-1] - station_locs) / (dist + 1e-6) / v
|
|
446
|
+
J[:, -1] = 1
|
|
447
|
+
return y, J
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def newton_method(
|
|
451
|
+
vars, data, station_locs, phase_type, weight, max_iter=20, convergence=1, vel={"p": 6.0, "s": 6.0 / 1.75}
|
|
452
|
+
):
|
|
453
|
+
for i in range(max_iter):
|
|
454
|
+
prev = vars.copy()
|
|
455
|
+
y, J = diff_and_grad(vars, data, station_locs, phase_type, vel=vel)
|
|
456
|
+
JTJ = np.dot(J.T, weight * J)
|
|
457
|
+
I = np.zeros_like(JTJ)
|
|
458
|
+
np.fill_diagonal(I, 1e-3)
|
|
459
|
+
vars -= np.dot(np.linalg.inv(JTJ + I), np.dot(J.T, y * weight)).T
|
|
460
|
+
if (np.sum(np.abs(vars - prev))) < convergence:
|
|
461
|
+
return vars
|
|
462
|
+
return vars
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
## l1 norm
|
|
466
|
+
# def loss_and_grad(vars, data, station_locs, phase_type, weight, vel={"p":6.0, "s":6.0/1.75}):
|
|
467
|
+
|
|
468
|
+
# v = np.array([vel[p] for p in phase_type])[:, np.newaxis]
|
|
469
|
+
# vars = vars[np.newaxis, :]
|
|
470
|
+
# dist = np.sqrt(np.sum((station_locs - vars[:,:-1])**2, axis=1, keepdims=True))
|
|
471
|
+
# J = np.zeros([data.shape[0], vars.shape[1]])
|
|
472
|
+
# J[:, :-1] = (vars[:,:-1] - station_locs)/(dist + 1e-6)/v
|
|
473
|
+
# J[:, -1] = 1
|
|
474
|
+
|
|
475
|
+
# loss = np.sum(np.abs(dist/v - (data[:,-1:] - vars[:,-1:])) * weight)
|
|
476
|
+
# J = np.sum(np.sign(dist/v - (data[:,-1:] - vars[:,-1:])) * weight * J, axis=0, keepdims=True)
|
|
477
|
+
|
|
478
|
+
# return loss, J
|