1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6import numpy as num
7import os
9from pyrocko import moment_tensor, model
10from pyrocko.gui.snuffling import Snuffling, Param, Choice, EventMarker, Switch
11from pyrocko import gf
13km = 1000.
16class Seismosizer(Snuffling):
17 '''
18 Generate synthetic traces on the fly
19 ====================================
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.).
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 '''
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]
36 def get_source(self, event):
37 raise NotImplementedError()
39 def panel_visibility_changed(self, bool):
40 if bool:
41 if self._engine is None:
42 self.set_engine()
44 def set_engine(self):
45 self._engine = None
46 self.store_ids = self.get_store_ids()
47 if self.store_ids == []:
48 return
50 self.set_parameter_choices('store_id', self.store_ids)
51 self.store_id = self.store_ids[0]
53 def get_engine(self):
54 if not self._engine:
55 self._engine = gf.LocalEngine(use_config=True)
57 return self._engine
59 def get_store_ids(self):
60 return self.get_engine().get_store_ids()
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
67 return stf
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]']))
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)
86 self.store_ids = None
87 self.offline_config = None
88 self._engine = None
90 def call(self):
91 '''
92 Main work routine of the snuffling.
93 '''
94 self.cleanup()
96 # get time range visible in viewer
97 viewer = self.get_viewer()
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 = []
108 stations = self.get_stations()
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()
119 s2c[ns].add((loc, cha))
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)
129 for s in stations:
130 ns = s.nsl()[:2]
131 if ns not in s2c:
132 s2c[ns] = set()
134 for cha in 'NEZ':
135 s2c[ns].add(('', cha))
137 source = self.get_source(event)
138 source.regularize()
140 m = EventMarker(source.pyrocko_event())
141 self.add_marker(m)
143 targets = []
145 if self.store_id == '<not loaded yet>':
146 self.fail('Select a GF Store first')
148 for station in stations:
150 nsl = station.nsl()
151 if nsl[:2] not in s2c:
152 continue
154 for loc, cha in s2c[nsl[:2]]:
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')
170 _, bazi = source.azibazi_to(target)
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
188 target.azimuth = azi
189 target.dip = dip
191 targets.append(target)
193 req = gf.Request(
194 sources=[source],
195 targets=targets)
197 req.regularize()
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)
204 traces = resp.pyrocko_traces()
206 if self.waveform_type.startswith('Velocity'):
207 for tr in traces:
208 tr.set_ydata(num.diff(tr.ydata) / tr.deltat)
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)
214 if self.waveform_type.endswith('[nm]') or \
215 self.waveform_type.endswith('[nm/s]') or \
216 self.waveform_type.endswith('[nm/s^2]'):
218 for tr in traces:
219 tr.set_ydata(tr.ydata * 1e9)
221 self.add_traces(traces)
223 def mechanism_from_event(self):
225 event = self.get_viewer().get_active_event()
227 if event is None:
228 self.fail('No active event set.')
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)
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)
250 self.set_parameter('lat', event.lat)
251 self.set_parameter('lon', event.lon)
252 self.set_parameter('depth_km', event.depth/km)
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()
263 self.set_parameter_choices('store_id', self.store_ids)
265 def has_config(self, directory):
266 return 'config' in os.listdir(directory)
269class DCSource(Seismosizer):
271 def setup(self):
272 '''Customization of the snuffling.'''
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))
300 Seismosizer.setup(self)
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())
317class SFSource(Seismosizer):
318 def setup(self):
319 '''Customization of the snuffling.'''
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))
345 Seismosizer.setup(self)
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())
361class RectangularSource(Seismosizer):
363 def setup(self):
364 '''Customization of the snuffling.'''
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))
402 Seismosizer.setup(self)
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())
424class PseudoDynamicRuptureSource(Seismosizer):
426 def setup(self):
427 '''Customization of the snuffling.'''
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))
469 self.add_parameter(Switch(
470 'Tapered tractions', 'tapered', True))
472 Seismosizer.setup(self)
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)
497 return source
500def __snufflings__():
501 '''Returns a list of snufflings to be exported by this module.'''
502 return [
503 DCSource(),
504 SFSource(),
505 RectangularSource(),
506 PseudoDynamicRuptureSource()]