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.
Files changed (49) hide show
  1. DASPy_toolbox-1.0.0.dist-info/LICENSE.txt +1 -0
  2. DASPy_toolbox-1.0.0.dist-info/METADATA +85 -0
  3. DASPy_toolbox-1.0.0.dist-info/RECORD +49 -0
  4. DASPy_toolbox-1.0.0.dist-info/WHEEL +5 -0
  5. DASPy_toolbox-1.0.0.dist-info/entry_points.txt +2 -0
  6. DASPy_toolbox-1.0.0.dist-info/top_level.txt +1 -0
  7. daspy/__init__.py +4 -0
  8. daspy/advanced_tools/__init__.py +0 -0
  9. daspy/advanced_tools/channel.py +354 -0
  10. daspy/advanced_tools/decomposition.py +165 -0
  11. daspy/advanced_tools/denoising.py +276 -0
  12. daspy/advanced_tools/fdct.py +789 -0
  13. daspy/advanced_tools/strain2vel.py +245 -0
  14. daspy/basic_tools/__init__.py +0 -0
  15. daspy/basic_tools/filter.py +257 -0
  16. daspy/basic_tools/freqattributes.py +117 -0
  17. daspy/basic_tools/preprocessing.py +238 -0
  18. daspy/basic_tools/visualization.py +186 -0
  19. daspy/core/__init__.py +4 -0
  20. daspy/core/collection.py +279 -0
  21. daspy/core/dasdatetime.py +72 -0
  22. daspy/core/example.pkl +0 -0
  23. daspy/core/make_example.py +32 -0
  24. daspy/core/read.py +544 -0
  25. daspy/core/section.py +1319 -0
  26. daspy/core/write.py +282 -0
  27. daspy/seismic_detection/__init__.py +1 -0
  28. daspy/seismic_detection/calc_travel_time.py +23 -0
  29. daspy/seismic_detection/core.py +119 -0
  30. daspy/seismic_detection/detection.py +12 -0
  31. daspy/seismic_detection/gamma/__init__.py +13 -0
  32. daspy/seismic_detection/gamma/_base.py +549 -0
  33. daspy/seismic_detection/gamma/_bayesian_mixture.py +875 -0
  34. daspy/seismic_detection/gamma/_gaussian_mixture.py +866 -0
  35. daspy/seismic_detection/gamma/app.py +192 -0
  36. daspy/seismic_detection/gamma/seismic_ops.py +478 -0
  37. daspy/seismic_detection/gamma/utils.py +512 -0
  38. daspy/seismic_detection/location.py +266 -0
  39. daspy/seismic_detection/magnitude.py +43 -0
  40. daspy/seismic_detection/phase_picking.py +67 -0
  41. daspy/structure_imaging/__init__.py +0 -0
  42. daspy/structure_imaging/ambient_noise.py +4 -0
  43. daspy/structure_imaging/dispersion.py +27 -0
  44. daspy/structure_imaging/fault_zone.py +59 -0
  45. daspy/structure_imaging/inversion.py +6 -0
  46. daspy/traffic_monitoring/JamDetection.py +6 -0
  47. daspy/traffic_monitoring/SpeedMeasurement.py +6 -0
  48. daspy/traffic_monitoring/VehicleDetection.py +6 -0
  49. 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