1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6import numpy as num 

7import os 

8 

9from pyrocko import moment_tensor, model 

10from ..snuffling import Snuffling, Param, Choice, Switch, EventMarker 

11from pyrocko import gf 

12 

13km = 1000. 

14 

15 

16class Seismosizer(Snuffling): 

17 ''' 

18 Generate synthetic traces on the fly 

19 ==================================== 

20 

21 Activate an event (press `e`) to generate synthetic waveforms for it. 

22 If no stations have been loaded pripor to execution, two stations will be 

23 generated at lat/lon = (5., 0) and (-5., 0.). 

24 

25 All geometrical units are kilometers (if not stated otherwise). 

26 `GF Stores` will be loaded on start from `gf_store_superdirs` defined 

27 in your pyrocko config file located at `$HOME/.pyrocko/config.pf`. 

28 ''' 

29 

30 def __init__(self): 

31 Snuffling.__init__(self) 

32 self.stf_types = ['half sin', 'triangular', 'boxcar', 'None'] 

33 self.stf_instances = [gf.HalfSinusoidSTF(), gf.TriangularSTF(), 

34 gf.BoxcarSTF(), None] 

35 

36 def get_source(self, event): 

37 raise NotImplementedError() 

38 

39 def panel_visibility_changed(self, bool): 

40 if bool: 

41 if self._engine is None: 

42 self.set_engine() 

43 

44 def set_engine(self): 

45 self._engine = None 

46 self.store_ids = self.get_store_ids() 

47 if self.store_ids == []: 

48 return 

49 

50 self.set_parameter_choices('store_id', self.store_ids) 

51 self.store_id = self.store_ids[0] 

52 

53 def get_engine(self): 

54 if not self._engine: 

55 self._engine = gf.LocalEngine(use_config=True) 

56 

57 return self._engine 

58 

59 def get_store_ids(self): 

60 return self.get_engine().get_store_ids() 

61 

62 def get_stf(self): 

63 stf = dict(zip(self.stf_types, self.stf_instances))[self.stf_type] 

64 if stf is not None: 

65 stf.duration = self.stf_duration 

66 

67 return stf 

68 

69 def setup(self): 

70 self.add_parameter( 

71 Choice('GF Store', 'store_id', 

72 '<not loaded yet>', ['<not loaded yet>'])) 

73 self.add_parameter( 

74 Choice('Waveform type', 'waveform_type', 'Displacement [m]', 

75 ['Displacement [m]', 

76 'Displacement [nm]', 

77 'Velocity [m/s]', 

78 'Velocity [nm/s]', 

79 'Acceleration [m/s^2]', 

80 'Acceleration [nm/s^2]'])) 

81 

82 self.add_trigger('Set Engine', self.set_engine) 

83 self.add_trigger('Set Params from Event', self.mechanism_from_event) 

84 self.add_trigger('Add Stores', self.add_store) 

85 

86 self.store_ids = None 

87 self.offline_config = None 

88 self._engine = None 

89 

90 def call(self): 

91 ''' 

92 Main work routine of the snuffling. 

93 ''' 

94 self.cleanup() 

95 

96 # get time range visible in viewer 

97 viewer = self.get_viewer() 

98 

99 event = viewer.get_active_event() 

100 if event: 

101 event, stations = self.get_active_event_and_stations( 

102 missing='warn') 

103 else: 

104 # event = model.Event(lat=self.lat, lon=self.lon) 

105 event = model.Event(lat=0., lon=0.) 

106 stations = [] 

107 

108 stations = self.get_stations() 

109 

110 s2c = {} 

111 for traces in self.chopper_selected_traces(fallback=True, 

112 mode='visible'): 

113 for tr in traces: 

114 net, sta, loc, cha = tr.nslc_id 

115 ns = net, sta 

116 if ns not in s2c: 

117 s2c[ns] = set() 

118 

119 s2c[ns].add((loc, cha)) 

120 

121 if not stations: 

122 stations = [] 

123 for (lat, lon) in [(5., 0.), (-5., 0.)]: 

124 s = model.Station(station='(%g, %g)' % (lat, lon), 

125 lat=lat, lon=lon) 

126 stations.append(s) 

127 viewer.add_stations(stations) 

128 

129 for s in stations: 

130 ns = s.nsl()[:2] 

131 if ns not in s2c: 

132 s2c[ns] = set() 

133 

134 for cha in 'NEZ': 

135 s2c[ns].add(('', cha)) 

136 

137 source = self.get_source(event) 

138 source.regularize() 

139 

140 m = EventMarker(source.pyrocko_event()) 

141 self.add_marker(m) 

142 

143 targets = [] 

144 

145 if self.store_id == '<not loaded yet>': 

146 self.fail('Select a GF Store first') 

147 

148 for station in stations: 

149 

150 nsl = station.nsl() 

151 if nsl[:2] not in s2c: 

152 continue 

153 

154 for loc, cha in s2c[nsl[:2]]: 

155 

156 target = gf.Target( 

157 codes=( 

158 station.network, 

159 station.station, 

160 loc + '-syn', 

161 cha), 

162 quantity='displacement', 

163 lat=station.lat, 

164 lon=station.lon, 

165 depth=station.depth, 

166 store_id=self.store_id, 

167 optimization='enable', 

168 interpolation='nearest_neighbor') 

169 

170 _, bazi = source.azibazi_to(target) 

171 

172 if cha.endswith('T'): 

173 dip = 0. 

174 azi = bazi + 270. 

175 elif cha.endswith('R'): 

176 dip = 0. 

177 azi = bazi + 180. 

178 elif cha.endswith('1'): 

179 dip = 0. 

180 azi = 0. 

181 elif cha.endswith('2'): 

182 dip = 0. 

183 azi = 90. 

184 else: 

185 dip = None 

186 azi = None 

187 

188 target.azimuth = azi 

189 target.dip = dip 

190 

191 targets.append(target) 

192 

193 req = gf.Request( 

194 sources=[source], 

195 targets=targets) 

196 

197 req.regularize() 

198 

199 try: 

200 resp = self.get_engine().process(req, nthreads=0) 

201 except (gf.meta.OutOfBounds, gf.store_ext.StoreExtError)as e: 

202 self.fail(e) 

203 

204 traces = resp.pyrocko_traces() 

205 

206 if self.waveform_type.startswith('Velocity'): 

207 for tr in traces: 

208 tr.set_ydata(num.diff(tr.ydata) / tr.deltat) 

209 

210 elif self.waveform_type.startswith('Acceleration'): 

211 for tr in traces: 

212 tr.set_ydata(num.diff(num.diff(tr.ydata)) / tr.deltat**2) 

213 

214 if self.waveform_type.endswith('[nm]') or \ 

215 self.waveform_type.endswith('[nm/s]') or \ 

216 self.waveform_type.endswith('[nm/s^2]'): 

217 

218 for tr in traces: 

219 tr.set_ydata(tr.ydata * 1e9) 

220 

221 self.add_traces(traces) 

222 

223 def mechanism_from_event(self): 

224 

225 event = self.get_viewer().get_active_event() 

226 

227 if event is None: 

228 self.fail('No active event set.') 

229 

230 if event.moment_tensor is not None: 

231 strike, dip, slip_rake = event.moment_tensor\ 

232 .both_strike_dip_rake()[0] 

233 moment = event.moment_tensor.scalar_moment() 

234 self.set_parameter('magnitude', 

235 moment_tensor.moment_to_magnitude(moment)) 

236 self.set_parameter('strike', strike) 

237 self.set_parameter('dip', dip) 

238 self.set_parameter('rake', slip_rake) 

239 else: 

240 self.warn( 

241 'No source mechanism available for event %s. ' 

242 'Only setting location' % event.name) 

243 

244 if event.duration is not None: 

245 self.set_parameter('stf_duration', event.duration) 

246 else: 

247 self.warn( 

248 'No source duration available for event %s. ' % event.name) 

249 

250 self.set_parameter('lat', event.lat) 

251 self.set_parameter('lon', event.lon) 

252 self.set_parameter('depth_km', event.depth/km) 

253 

254 def add_store(self): 

255 self._engine = self.get_engine() 

256 superdir = self.input_directory() 

257 if self.has_config(superdir): 

258 self._engine.store_dirs.append(superdir) 

259 else: 

260 self._engine.store_superdirs.append(superdir) 

261 self.store_ids = self._engine.get_store_ids() 

262 

263 self.set_parameter_choices('store_id', self.store_ids) 

264 

265 def has_config(self, directory): 

266 return 'config' in os.listdir(directory) 

267 

268 

269class DCSource(Seismosizer): 

270 

271 def setup(self): 

272 '''Customization of the snuffling.''' 

273 

274 self.set_name('Seismosizer: DCSource') 

275 self.add_parameter( 

276 Param('Time', 'time', 0.0, -50., 50.)) 

277 # self.add_parameter( 

278 # Param('Latitude', 'lat', 0.0, -90., 90.)) 

279 # self.add_parameter( 

280 # Param('Longitude', 'lon', 0.0, -180., 180.)) 

281 self.add_parameter( 

282 Param('North shift', 'north_km', 0.0, -50., 50.)) 

283 self.add_parameter( 

284 Param('East shift', 'east_km', 0.0, -50., 50.)) 

285 self.add_parameter( 

286 Param('Depth', 'depth_km', 10.0, -100.0, 600.0)) 

287 self.add_parameter( 

288 Param('Magnitude', 'magnitude', 6.0, 0.0, 10.0)) 

289 self.add_parameter( 

290 Param('Strike', 'strike', 0., -180., 180.)) 

291 self.add_parameter( 

292 Param('Dip', 'dip', 90., 0., 90.)) 

293 self.add_parameter( 

294 Param('Rake', 'rake', 0., -180., 180.)) 

295 self.add_parameter( 

296 Param('STF duration', 'stf_duration', 0., 0., 20.)) 

297 self.add_parameter( 

298 Choice('STF type', 'stf_type', self.stf_types[0], self.stf_types)) 

299 

300 Seismosizer.setup(self) 

301 

302 def get_source(self, event): 

303 return gf.DCSource( 

304 time=event.time+self.time, 

305 lat=event.lat, 

306 lon=event.lon, 

307 north_shift=self.north_km*km, 

308 east_shift=self.east_km*km, 

309 depth=self.depth_km*km, 

310 magnitude=self.magnitude, 

311 strike=self.strike, 

312 dip=self.dip, 

313 rake=self.rake, 

314 stf=self.get_stf()) 

315 

316 

317class SFSource(Seismosizer): 

318 def setup(self): 

319 '''Customization of the snuffling.''' 

320 

321 self.set_name('Seismosizer: SFSource') 

322 self.add_parameter( 

323 Param('Time', 'time', 0.0, -50., 50.)) 

324 # self.add_parameter( 

325 # Param('Latitude', 'lat', 0.0, -90., 90.)) 

326 # self.add_parameter( 

327 # Param('Longitude', 'lon', 0.0, -180., 180.)) 

328 self.add_parameter( 

329 Param('North shift', 'north_km', 0.0, -50., 50.)) 

330 self.add_parameter( 

331 Param('East shift', 'east_km', 0.0, -50., 50.)) 

332 self.add_parameter( 

333 Param('Depth', 'depth_km', 10.0, -100.0, 600.0)) 

334 self.add_parameter( 

335 Param('North force', 'fn', 1e3, -1e9, 1e9)) 

336 self.add_parameter( 

337 Param('East force', 'fe', 1e3, -1e9, 1e9)) 

338 self.add_parameter( 

339 Param('Down force', 'fd', 1e3, -1e9, 1e9)) 

340 self.add_parameter( 

341 Param('STF duration', 'stf_duration', 0., 0., 100.)) 

342 self.add_parameter( 

343 Choice('STF type', 'stf_type', self.stf_types[0], self.stf_types)) 

344 

345 Seismosizer.setup(self) 

346 

347 def get_source(self, event): 

348 return gf.SFSource( 

349 time=event.time+self.time, 

350 lat=event.lat, 

351 lon=event.lon, 

352 north_shift=self.north_km*km, 

353 east_shift=self.east_km*km, 

354 depth=self.depth_km*km, 

355 fn=self.fn, 

356 fe=self.fe, 

357 fd=self.fd, 

358 stf=self.get_stf()) 

359 

360 

361class RectangularSource(Seismosizer): 

362 

363 def setup(self): 

364 '''Customization of the snuffling.''' 

365 

366 self.set_name('Seismosizer: RectangularSource') 

367 self.add_parameter( 

368 Param('Time', 'time', 0.0, -50., 50.)) 

369 # self.add_parameter( 

370 # Param('Latitude', 'lat', 0.0, -90., 90.)) 

371 # self.add_parameter( 

372 # Param('Longitude', 'lon', 0.0, -180., 180.)) 

373 self.add_parameter( 

374 Param('North shift', 'north_km', 0.0, -50., 50.)) 

375 self.add_parameter( 

376 Param('East shift', 'east_km', 0.0, -50., 50.)) 

377 self.add_parameter( 

378 Param('Depth', 'depth_km', 10.0, 0.0, 600.0)) 

379 self.add_parameter( 

380 Param('Magnitude', 'magnitude', 6.0, 0.0, 10.0)) 

381 self.add_parameter( 

382 Param('Strike', 'strike', 0., -180., 180.)) 

383 self.add_parameter( 

384 Param('Dip', 'dip', 90., 0., 90.)) 

385 self.add_parameter( 

386 Param('Rake', 'rake', 0., -180., 180.)) 

387 self.add_parameter( 

388 Param('Length', 'length', 10.*km, .1*km, 100*km)) 

389 self.add_parameter( 

390 Param('Width', 'width', 5.*km, .1*km, 50*km)) 

391 self.add_parameter( 

392 Param('Nucleation X', 'nucleation_x', 0., -1., 1.)) 

393 self.add_parameter( 

394 Param('Nucleation Y', 'nucleation_y', 0., -1., 1.)) 

395 self.add_parameter( 

396 Param('Rupture velocity', 'velocity', 3500.0, 0.0, 5000.0)) 

397 self.add_parameter( 

398 Param('STF duration', 'stf_duration', 0., 0., 20.)) 

399 self.add_parameter( 

400 Choice('STF type', 'stf_type', self.stf_types[0], self.stf_types)) 

401 

402 Seismosizer.setup(self) 

403 

404 def get_source(self, event): 

405 return gf.RectangularSource( 

406 time=event.time+self.time, 

407 lat=event.lat, 

408 lon=event.lon, 

409 north_shift=self.north_km*km, 

410 east_shift=self.east_km*km, 

411 depth=self.depth_km*km, 

412 magnitude=self.magnitude, 

413 strike=self.strike, 

414 dip=self.dip, 

415 rake=self.rake, 

416 length=self.length, 

417 width=self.width, 

418 nucleation_x=self.nucleation_x, 

419 nucleation_y=self.nucleation_y, 

420 velocity=self.velocity, 

421 stf=self.get_stf()) 

422 

423 

424class PseudoDynamicRuptureSource(Seismosizer): 

425 

426 def setup(self): 

427 '''Customization of the snuffling.''' 

428 

429 self.set_name('Seismosizer: PseudoDynamicRupture') 

430 self.add_parameter( 

431 Param('Time', 'time', 0.0, -50., 50.)) 

432 # self.add_parameter( 

433 # Param('Latitude', 'lat', 0.0, -90., 90.)) 

434 # self.add_parameter( 

435 # Param('Longitude', 'lon', 0.0, -180., 180.)) 

436 self.add_parameter( 

437 Param('North shift', 'north_km', 0.0, -50., 50.)) 

438 self.add_parameter( 

439 Param('East shift', 'east_km', 0.0, -50., 50.)) 

440 self.add_parameter( 

441 Param('Depth', 'depth_km', 10.0, 0.0, 600.0)) 

442 self.add_parameter( 

443 Param('Slip', 'slip', 1.0, 0.0, 20.0)) 

444 self.add_parameter( 

445 Param('Strike', 'strike', 0., -180., 180.)) 

446 self.add_parameter( 

447 Param('Dip', 'dip', 90., 0., 90.)) 

448 self.add_parameter( 

449 Param('Rake', 'rake', 0., -180., 180.)) 

450 self.add_parameter( 

451 Param('Length', 'length', 10.*km, .1*km, 300*km)) 

452 self.add_parameter( 

453 Param('Width', 'width', 5.*km, .1*km, 50*km)) 

454 self.add_parameter( 

455 Param('Nucleation X', 'nucleation_x', 0., -1., 1.)) 

456 self.add_parameter( 

457 Param('Nucleation Y', 'nucleation_y', 0., -1., 1.)) 

458 self.add_parameter( 

459 Param('Gamma', 'gamma', 0.8, 0.1, 1.5)) 

460 self.add_parameter( 

461 Param('nx', 'nx', 5, 1, 20)) 

462 self.add_parameter( 

463 Param('ny', 'ny', 5, 1, 20)) 

464 self.add_parameter( 

465 Param('STF duration', 'stf_duration', 0., 0., 20.)) 

466 self.add_parameter( 

467 Choice('STF type', 'stf_type', self.stf_types[0], self.stf_types)) 

468 

469 self.add_parameter(Switch( 

470 'Tapered tractions', 'tapered', True)) 

471 

472 Seismosizer.setup(self) 

473 

474 def get_source(self, event): 

475 source = gf.PseudoDynamicRupture( 

476 time=event.time + self.time, 

477 lat=event.lat, 

478 lon=event.lon, 

479 nx=int(self.nx), 

480 ny=int(self.ny), 

481 north_shift=self.north_km*km, 

482 east_shift=self.east_km*km, 

483 depth=self.depth_km*km, 

484 slip=self.slip, 

485 strike=self.strike, 

486 dip=self.dip, 

487 rake=self.rake, 

488 length=self.length, 

489 width=self.width, 

490 nucleation_x=self.nucleation_x, 

491 nucleation_y=self.nucleation_y, 

492 gamma=self.gamma, 

493 nthreads=5, 

494 pure_shear=True, 

495 smooth_rupture=True) 

496 

497 return source 

498 

499 

500def __snufflings__(): 

501 '''Returns a list of snufflings to be exported by this module.''' 

502 return [ 

503 DCSource(), 

504 SFSource(), 

505 RectangularSource(), 

506 PseudoDynamicRuptureSource()]