A seismology toolkit for Python https://pyrocko.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1141 lines
41 KiB

3 years ago
8 months ago
8 months ago
3 years ago
3 years ago
3 years ago
3 years ago
8 months ago
8 months ago
8 months ago
8 months ago
  1. # http://pyrocko.org - GPLv3
  2. #
  3. # The Pyrocko Developers, 21st Century
  4. # ---|P------/S----------~Lg----------
  5. from __future__ import absolute_import, division
  6. import numpy as num
  7. import logging
  8. import os
  9. import shutil
  10. import glob
  11. import copy
  12. import signal
  13. import math
  14. import time
  15. from tempfile import mkdtemp
  16. from subprocess import Popen, PIPE
  17. from os.path import join as pjoin
  18. from pyrocko import trace, util, cake, gf
  19. from pyrocko.guts import Object, Float, String, Bool, Tuple, Int, List
  20. from pyrocko.moment_tensor import MomentTensor, symmat6
  21. guts_prefix = 'pf'
  22. logger = logging.getLogger('pyrocko.fomosto.qssp')
  23. # how to call the programs
  24. program_bins = {
  25. 'qssp.2010beta': 'fomosto_qssp2010beta',
  26. 'qssp.2010': 'fomosto_qssp2010',
  27. 'qssp.2017': 'fomosto_qssp2017',
  28. 'qssp.ppeg2017': 'fomosto_qsspppeg2017',
  29. }
  30. def have_backend():
  31. have_any = False
  32. for cmd in [[exe] for exe in program_bins.values()]:
  33. try:
  34. p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
  35. (stdout, stderr) = p.communicate()
  36. have_any = True
  37. except OSError:
  38. pass
  39. return have_any
  40. qssp_components = {
  41. 1: 'ae an az gr sd te tn ue un uz ve vn vz'.split(),
  42. 2: 'ar at ap gr sd tt tp ur ut up vr vt vp'.split(),
  43. 3: '_rota_e _rota_n _rota_z'.split(),
  44. 4: '_gravitation_e _gravitation_n _gravitation_z '
  45. '_acce_e _acce_n _acce_z'.split()
  46. }
  47. def str_float_vals(vals):
  48. return ' '.join(['%.6e' % val for val in vals])
  49. def cake_model_to_config(mod):
  50. k = 1000.
  51. srows = []
  52. for i, row in enumerate(mod.to_scanlines()):
  53. depth, vp, vs, rho, qp, qs = row
  54. row = [depth/k, vp/k, vs/k, rho/k, qp, qs]
  55. srows.append('%i %s' % (i+1, str_float_vals(row)))
  56. return '\n'.join(srows), len(srows)
  57. class QSSPSource(Object):
  58. lat = Float.T(default=0.0)
  59. lon = Float.T(default=0.0)
  60. depth = Float.T(default=10.0)
  61. torigin = Float.T(default=0.0)
  62. trise = Float.T(default=1.0)
  63. def string_for_config(self):
  64. return '%(lat)15e %(lon)15e %(depth)15e %(torigin)15e %(trise)15e' \
  65. % self.__dict__
  66. class QSSPSourceMT(QSSPSource):
  67. munit = Float.T(default=1.0)
  68. mrr = Float.T(default=1.0)
  69. mtt = Float.T(default=1.0)
  70. mpp = Float.T(default=1.0)
  71. mrt = Float.T(default=0.0)
  72. mrp = Float.T(default=0.0)
  73. mtp = Float.T(default=0.0)
  74. def string_for_config(self):
  75. return '%(munit)15e %(mrr)15e %(mtt)15e %(mpp)15e ' \
  76. '%(mrt)15e %(mrp)15e %(mtp)15e ' \
  77. % self.__dict__ + QSSPSource.string_for_config(self)
  78. class QSSPSourceDC(QSSPSource):
  79. moment = Float.T(default=1.0e9)
  80. strike = Float.T(default=0.0)
  81. dip = Float.T(default=90.0)
  82. rake = Float.T(default=0.0)
  83. def string_for_config(self):
  84. return '%(moment)15e %(strike)15e %(dip)15e %(rake)15e ' \
  85. % self.__dict__ + QSSPSource.string_for_config(self)
  86. class QSSPReceiver(Object):
  87. lat = Float.T(default=10.0)
  88. lon = Float.T(default=0.0)
  89. name = String.T(default='')
  90. tstart = Float.T(default=0.0)
  91. distance = Float.T(default=0.0)
  92. def string_for_config(self):
  93. return "%(lat)15e %(lon)15e '%(name)s' %(tstart)e" % self.__dict__
  94. class QSSPGreen(Object):
  95. depth = Float.T(default=10.0)
  96. filename = String.T(default='GF_10km')
  97. calculate = Bool.T(default=True)
  98. def string_for_config(self):
  99. return "%(depth)15e '%(filename)s' %(calculate)i" % self.__dict__
  100. class QSSPConfig(Object):
  101. qssp_version = String.T(default='2010beta')
  102. time_region = Tuple.T(2, gf.Timing.T(), default=(
  103. gf.Timing('-10'), gf.Timing('+890')))
  104. frequency_max = Float.T(optional=True)
  105. slowness_max = Float.T(default=0.4)
  106. antialiasing_factor = Float.T(default=0.1)
  107. # only available in 2017:
  108. switch_turning_point_filter = Int.T(default=0)
  109. max_pene_d1 = Float.T(default=2891.5)
  110. max_pene_d2 = Float.T(default=6371.0)
  111. earth_radius = Float.T(default=6371.0)
  112. switch_free_surf_reflection = Int.T(default=1)
  113. lowpass_order = Int.T(default=0, optional=True)
  114. lowpass_corner = Float.T(default=1.0, optional=True)
  115. bandpass_order = Int.T(default=0, optional=True)
  116. bandpass_corner_low = Float.T(default=1.0, optional=True)
  117. bandpass_corner_high = Float.T(default=1.0, optional=True)
  118. output_slowness_min = Float.T(default=0.0, optional=True)
  119. output_slowness_max = Float.T(optional=True)
  120. spheroidal_modes = Bool.T(default=True)
  121. toroidal_modes = Bool.T(default=True)
  122. # only available in 2010beta:
  123. cutoff_harmonic_degree_sd = Int.T(optional=True, default=0)
  124. cutoff_harmonic_degree_min = Int.T(default=0)
  125. cutoff_harmonic_degree_max = Int.T(default=25000)
  126. crit_frequency_sge = Float.T(default=0.0)
  127. crit_harmonic_degree_sge = Int.T(default=0)
  128. include_physical_dispersion = Bool.T(default=False)
  129. source_patch_radius = Float.T(default=0.0)
  130. cut = Tuple.T(2, gf.Timing.T(), optional=True)
  131. fade = Tuple.T(4, gf.Timing.T(), optional=True)
  132. relevel_with_fade_in = Bool.T(default=False)
  133. nonzero_fade_in = Bool.T(default=False)
  134. nonzero_fade_out = Bool.T(default=False)
  135. def items(self):
  136. return dict(self.T.inamevals(self))
  137. class QSSPConfigFull(QSSPConfig):
  138. time_window = Float.T(default=900.0)
  139. receiver_depth = Float.T(default=0.0)
  140. sampling_interval = Float.T(default=5.0)
  141. output_filename = String.T(default='receivers')
  142. output_format = Int.T(default=1)
  143. output_time_window = Float.T(optional=True)
  144. gf_directory = String.T(default='qssp_green')
  145. greens_functions = List.T(QSSPGreen.T())
  146. sources = List.T(QSSPSource.T())
  147. receivers = List.T(QSSPReceiver.T())
  148. earthmodel_1d = gf.meta.Earthmodel1D.T(optional=True)
  149. @staticmethod
  150. def example():
  151. conf = QSSPConfigFull()
  152. conf.sources.append(QSSPSourceMT())
  153. lats = [20.]
  154. conf.receivers.extend(QSSPReceiver(lat=lat) for lat in lats)
  155. conf.greens_functions.append(QSSPGreen())
  156. return conf
  157. @property
  158. def components(self):
  159. if self.qssp_version == '2017':
  160. fmt = 3
  161. elif self.qssp_version == 'ppeg2017':
  162. fmt = 4
  163. else:
  164. fmt = self.output_format
  165. return qssp_components[fmt]
  166. def get_output_filenames(self, rundir):
  167. if self.qssp_version in ('2017', 'ppeg2017'):
  168. return [
  169. pjoin(rundir, self.output_filename + c + '.dat')
  170. for c in self.components]
  171. else:
  172. return [
  173. pjoin(rundir, self.output_filename + '.' + c)
  174. for c in self.components]
  175. def ensure_gf_directory(self):
  176. util.ensuredir(self.gf_directory)
  177. def string_for_config(self):
  178. def aggregate(l):
  179. return len(l), '\n'.join(x.string_for_config() for x in l)
  180. assert len(self.greens_functions) > 0
  181. assert len(self.sources) > 0
  182. assert len(self.receivers) > 0
  183. d = self.__dict__.copy()
  184. if self.output_time_window is None:
  185. d['output_time_window'] = self.time_window
  186. if self.output_slowness_max is None:
  187. d['output_slowness_max'] = self.slowness_max
  188. if self.frequency_max is None:
  189. d['frequency_max'] = 0.5/self.sampling_interval
  190. d['gf_directory'] = os.path.abspath(self.gf_directory) + '/'
  191. d['n_receiver_lines'], d['receiver_lines'] = aggregate(self.receivers)
  192. d['n_source_lines'], d['source_lines'] = aggregate(self.sources)
  193. d['n_gf_lines'], d['gf_lines'] = aggregate(self.greens_functions)
  194. model_str, nlines = cake_model_to_config(self.earthmodel_1d)
  195. d['n_model_lines'] = nlines
  196. d['model_lines'] = model_str
  197. if len(self.sources) == 0 or isinstance(self.sources[0], QSSPSourceMT):
  198. d['point_source_type'] = 1
  199. else:
  200. d['point_source_type'] = 2
  201. if self.qssp_version == '2010beta':
  202. d['scutoff_doc'] = '''
  203. # (SH waves), and cutoff harmonic degree for static deformation
  204. '''.strip()
  205. d['scutoff'] = '%i' % self.cutoff_harmonic_degree_sd
  206. d['sfilter_doc'] = '''
  207. # 3. selection of order of Butterworth low-pass filter (if <= 0, then no
  208. # filtering), corner frequency (smaller than the cut-off frequency defined
  209. # above)
  210. '''.strip()
  211. if self.bandpass_order != 0:
  212. raise QSSPError(
  213. 'this version of qssp does not support bandpass '
  214. 'settings, use lowpass instead')
  215. d['sfilter'] = '%i %f' % (
  216. self.lowpass_order,
  217. self.lowpass_corner)
  218. elif self.qssp_version in ('2010', '2017', 'ppeg2017'):
  219. d['scutoff_doc'] = '''
  220. # (SH waves), minimum and maximum cutoff harmonic degrees
  221. # Note: if the near-field static displacement is desired, the minimum
  222. # cutoff harmonic degree should not be smaller than, e.g., 2000.
  223. '''.strip()
  224. d['scutoff'] = '%i %i' % (
  225. self.cutoff_harmonic_degree_min,
  226. self.cutoff_harmonic_degree_max)
  227. d['sfilter_doc'] = '''
  228. # 3. selection of order of Butterworth bandpass filter (if <= 0, then no
  229. # filtering), lower and upper corner frequencies (smaller than the cut-off
  230. # frequency defined above)
  231. '''.strip()
  232. if self.lowpass_order != 0:
  233. raise QSSPError(
  234. 'this version of qssp does not support lowpass settings, '
  235. 'use bandpass instead')
  236. d['sfilter'] = '%i %f %f' % (
  237. self.bandpass_order,
  238. self.bandpass_corner_low,
  239. self.bandpass_corner_high)
  240. if self.qssp_version in ('2017', 'ppeg2017'):
  241. template = '''# autogenerated QSSP input by qssp.py
  242. #
  243. # This is the input file of FORTRAN77 program "qssp2017" for calculating
  244. # synthetic seismograms of a self-gravitating, spherically symmetric,
  245. # isotropic and viscoelastic earth.
  246. #
  247. # by
  248. # Rongjiang Wang <wang@gfz-potsdam.de>
  249. # Helmholtz-Centre Potsdam
  250. # GFZ German Reseach Centre for Geosciences
  251. # Telegrafenberg, D-14473 Potsdam, Germany
  252. #
  253. # Last modified: Potsdam, October 2017
  254. #
  255. # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  256. # If not specified, SI Unit System is used overall!
  257. #
  258. # Coordinate systems:
  259. # spherical (r,t,p) with r = radial,
  260. # t = co-latitude,
  261. # p = east longitude.
  262. # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  263. #
  264. # UNIFORM RECEIVER DEPTH
  265. # ======================
  266. # 1. uniform receiver depth [km]
  267. #-------------------------------------------------------------------------------------------
  268. %(receiver_depth)e
  269. #-------------------------------------------------------------------------------------------
  270. #
  271. # SPACE-TIME SAMPLING PARAMETERS
  272. # =========================
  273. # 1. time window [sec], sampling interval [sec]
  274. # 2. max. frequency [Hz] of Green's functions
  275. # 3. max. slowness [s/km] of Green's functions
  276. # Note: if the near-field static displacement is desired, the maximum slowness should not
  277. # be smaller than the S wave slowness in the receiver layer
  278. # 4. anti-aliasing factor (> 0 & < 1), if it is <= 0 or >= 1/e (~ 0.4), then
  279. # default value of 1/e is used (e.g., 0.1 = alias phases will be suppressed
  280. # to 10%% of their original amplitude)
  281. # 5. switch (1/0 = yes/no) of turning-point filter, the range (d1, d2) of max. penetration
  282. # depth [km] (d1 is meaningless if it is smaller than the receiver/source depth, and
  283. # d2 is meaningless if it is equal to or larger than the earth radius)
  284. #
  285. # Note: The turning-point filter (Line 5) works only for the extended QSSP code (e.g.,
  286. # qssp2016). if this filter is selected, all phases with the turning point
  287. # shallower than d1 or deeper than d2 will be filtered.
  288. #
  289. # 6. Earth radius [km], switch of free-surface-reflection filter (1/0 = with/without free
  290. # surface reflection)
  291. #
  292. # Note: The free-surface-reflection filter (Line 6) works only for the extended QSSP
  293. # code (e.g., qssp2016). if this filter is selected, all phases with the turning
  294. # point shallower than d1 or deeper than d2 will be filtered.
  295. #-------------------------------------------------------------------------------------------
  296. %(time_window)e %(sampling_interval)e
  297. %(frequency_max)e
  298. %(slowness_max)e
  299. %(antialiasing_factor)e
  300. %(switch_turning_point_filter)i %(max_pene_d1)e %(max_pene_d2)e
  301. %(earth_radius)e %(switch_free_surf_reflection)i
  302. #-------------------------------------------------------------------------------------------
  303. #
  304. # SELF-GRAVITATING EFFECT
  305. # =======================
  306. # 1. the critical frequency [Hz] and the critical harmonic degree, below which
  307. # the self-gravitating effect should be included
  308. #-------------------------------------------------------------------------------------------
  309. %(crit_frequency_sge)e %(crit_harmonic_degree_sge)i
  310. #-------------------------------------------------------------------------------------------
  311. #
  312. # WAVE TYPES
  313. # ==========
  314. # 1. selection (1/0 = yes/no) of speroidal modes (P-SV waves), selection of toroidal modes
  315. %(scutoff_doc)s
  316. #-------------------------------------------------------------------------------------------
  317. %(spheroidal_modes)i %(toroidal_modes)i %(scutoff)s
  318. #-------------------------------------------------------------------------------------------
  319. # GREEN'S FUNCTION FILES
  320. # ======================
  321. # 1. number of discrete source depths, estimated radius of each source patch [km] and
  322. # directory for Green's functions
  323. # 2. list of the source depths [km], the respective file names of the Green's
  324. # functions (spectra) and the switch number (0/1) (0 = do not calculate
  325. # this Green's function because it exists already, 1 = calculate or update
  326. # this Green's function. Note: update is required if any of the above
  327. # parameters is changed)
  328. #-------------------------------------------------------------------------------------------
  329. %(n_gf_lines)i %(source_patch_radius)e '%(gf_directory)s'
  330. %(gf_lines)s
  331. #--------------------------------------------------------------------------------------------------------
  332. #
  333. # MULTI-EVENT SOURCE PARAMETERS
  334. # =============================
  335. # 1. number of discrete point sources and selection of the source data format
  336. # (1, 2 or 3)
  337. # 2. list of the multi-event sources
  338. #
  339. # Format 1 (full moment tensor):
  340. # Unit Mrr Mtt Mpp Mrt Mrp Mtp Lat Lon Depth T_origin T_rise
  341. # [Nm] [deg] [deg] [km] [sec] [sec]
  342. #
  343. # Format 2 (double couple):
  344. # Unit Strike Dip Rake Lat Lon Depth T_origin T_rise
  345. # [Nm] [deg] [deg] [deg] [deg] [deg] [km] [sec] [sec]
  346. #
  347. # Format 3 (single force):
  348. # Unit Feast Fnorth Fvertical Lat Lon Depth T_origin T_rise
  349. # [N] [deg] [deg] [km] [sec] [sec]
  350. #
  351. # Note: for each point source, the default moment (force) rate time function is used, defined by a
  352. # squared half-period (T_rise) sinusoid starting at T_origin.
  353. #-----------------------------------------------------------------------------------
  354. %(n_source_lines)i %(point_source_type)i
  355. %(source_lines)s
  356. #--------------------------------------------------------------------------------------------------------
  357. #
  358. # RECEIVER PARAMETERS
  359. # ===================
  360. # 1. select output observables (1/0 = yes/no)
  361. # Note: the gravity change defined here is space based, i.e., the effect due to free-air
  362. # gradient and inertial are not included. the vertical component is positve upwards.
  363. # 2. output file name
  364. # 3. output time window [sec] (<= Green's function time window)
  365. %(sfilter_doc)s
  366. # 5. lower and upper slowness cut-off [s/km] (slowness band-pass filter)
  367. # 6. number of receiver
  368. # 7. list of the station parameters
  369. # Format:
  370. # Lat Lon Name Time_reduction
  371. # [deg] [deg] [sec]
  372. # (Note: Time_reduction = start time of the time window)
  373. #---------------------------------------------------------------------------------------------------------------
  374. # disp | velo | acce | strain | strain_rate | stress | stress_rate | rotation | rot_rate | gravitation | gravity
  375. #---------------------------------------------------------------------------------------------------------------
  376. # 1 1 1 1 1 1 1 1 1 1 1
  377. 0 0 0 0 0 0 0 1 0 0 0
  378. '%(output_filename)s'
  379. %(output_time_window)e
  380. %(sfilter)s
  381. %(output_slowness_min)e %(output_slowness_max)e
  382. %(n_receiver_lines)i
  383. %(receiver_lines)s
  384. #-------------------------------------------------------------------------------------------
  385. #
  386. # LAYERED EARTH MODEL (IASP91)
  387. # ============================
  388. # 1. number of data lines of the layered model and selection for including
  389. # the physical dispersion according Kamamori & Anderson (1977)
  390. #-------------------------------------------------------------------------------------------
  391. %(n_model_lines)i %(include_physical_dispersion)i
  392. #--------------------------------------------------------------------------------------------------------
  393. #
  394. # MODEL PARAMETERS
  395. # ================
  396. # no depth[km] vp[km/s] vs[km/s] ro[g/cm^3] qp qs
  397. #-------------------------------------------------------------------------------------------
  398. %(model_lines)s
  399. #---------------------------------end of all inputs-----------------------------------------
  400. ''' # noqa
  401. else:
  402. template = '''# autogenerated QSSP input by qssp.py
  403. #
  404. # This is the input file of FORTRAN77 program "qssp2010" for calculating
  405. # synthetic seismograms of a self-gravitating, spherically symmetric,
  406. # isotropic and viscoelastic earth.
  407. #
  408. # by
  409. # Rongjiang Wang <wang@gfz-potsdam.de>
  410. # Helmholtz-Centre Potsdam
  411. # GFZ German Reseach Centre for Geosciences
  412. # Telegrafenberg, D-14473 Potsdam, Germany
  413. #
  414. # Last modified: Potsdam, July, 2010
  415. #
  416. # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  417. # If not specified, SI Unit System is used overall!
  418. #
  419. # Coordinate systems:
  420. # spherical (r,t,p) with r = radial,
  421. # t = co-latitude,
  422. # p = east longitude.
  423. # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  424. #
  425. # UNIFORM RECEIVER DEPTH
  426. # ======================
  427. # 1. uniform receiver depth [km]
  428. #-------------------------------------------------------------------------------------------
  429. %(receiver_depth)e
  430. #-------------------------------------------------------------------------------------------
  431. #
  432. # TIME (FREQUENCY) SAMPLING
  433. # =========================
  434. # 1. time window [sec], sampling interval [sec]
  435. # 2. max. frequency [Hz] of Green's functions
  436. # 3. max. slowness [s/km] of Green's functions
  437. # Note: if the near-field static displacement is desired, the maximum slowness should not
  438. # be smaller than the S wave slowness in the receiver layer
  439. # 4. anti-aliasing factor (> 0 & < 1), if it is <= 0 or >= 1/e (~ 0.4), then
  440. # default value of 1/e is used (e.g., 0.1 = alias phases will be suppressed
  441. # to 10%% of their original amplitude)
  442. #
  443. # Note: The computation effort increases linearly the time window and
  444. # quadratically with the cut-off frequency.
  445. #-------------------------------------------------------------------------------------------
  446. %(time_window)e %(sampling_interval)e
  447. %(frequency_max)e
  448. %(slowness_max)e
  449. %(antialiasing_factor)e
  450. #-------------------------------------------------------------------------------------------
  451. #
  452. # SELF-GRAVITATING EFFECT
  453. # =======================
  454. # 1. the critical frequency [Hz] and the critical harmonic degree, below which
  455. # the self-gravitating effect should be included
  456. #-------------------------------------------------------------------------------------------
  457. %(crit_frequency_sge)e %(crit_harmonic_degree_sge)i
  458. #-------------------------------------------------------------------------------------------
  459. #
  460. # WAVE TYPES
  461. # ==========
  462. # 1. selection (1/0 = yes/no) of speroidal modes (P-SV waves), selection of toroidal modes
  463. %(scutoff_doc)s
  464. #-------------------------------------------------------------------------------------------
  465. %(spheroidal_modes)i %(toroidal_modes)i %(scutoff)s
  466. #-------------------------------------------------------------------------------------------
  467. # GREEN'S FUNCTION FILES
  468. # ======================
  469. # 1. number of discrete source depths, estimated radius of each source patch [km] and
  470. # directory for Green's functions
  471. # 2. list of the source depths [km], the respective file names of the Green's
  472. # functions (spectra) and the switch number (0/1) (0 = do not calculate
  473. # this Green's function because it exists already, 1 = calculate or update
  474. # this Green's function. Note: update is required if any of the above
  475. # parameters is changed)
  476. #-------------------------------------------------------------------------------------------
  477. %(n_gf_lines)i %(source_patch_radius)e '%(gf_directory)s'
  478. %(gf_lines)s
  479. #-------------------------------------------------------------------------------------------
  480. #
  481. # MULTI-EVENT SOURCE PARAMETERS
  482. # =============================
  483. # 1. number of discrete point sources and selection of the source data format
  484. # (1 or 2)
  485. # 2. list of the multi-event sources
  486. # Format 1:
  487. # M-Unit Mrr Mtt Mpp Mrt Mrp Mtp Lat Lon Depth T_origin T_rise
  488. # [Nm] [deg] [deg] [km] [sec] [sec]
  489. # Format 2:
  490. # Moment Strike Dip Rake Lat Lon Depth T_origin T_rise
  491. # [Nm] [deg] [deg] [deg] [deg] [deg] [km] [sec] [sec]
  492. #-------------------------------------------------------------------------------------------
  493. %(n_source_lines)i %(point_source_type)i
  494. %(source_lines)s
  495. #-------------------------------------------------------------------------------------------
  496. #
  497. # RECEIVER PARAMETERS
  498. # ===================
  499. # 1. output file name and and selection of output format:
  500. # 1 = cartesian: vertical(z)/north(n)/east(e);
  501. # 2 = spherical: radial(r)/theta(t)/phi(p)
  502. # (Note: if output format 2 is selected, the epicenter (T_origin = 0)
  503. # 2. output time window [sec] (<= Green's function time window)
  504. %(sfilter_doc)s
  505. # 4. lower and upper slowness cut-off [s/km] (slowness band-pass filter)
  506. # 5. number of receiver
  507. # 6. list of the station parameters
  508. # Format:
  509. # Lat Lon Name Time_reduction
  510. # [deg] [deg] [sec]
  511. # (Note: Time_reduction = start time of the time window)
  512. #-------------------------------------------------------------------------------------------
  513. '%(output_filename)s' %(output_format)i
  514. %(output_time_window)e
  515. %(sfilter)s
  516. %(output_slowness_min)e %(output_slowness_max)e
  517. %(n_receiver_lines)i
  518. %(receiver_lines)s
  519. #-------------------------------------------------------------------------------------------
  520. #
  521. # LAYERED EARTH MODEL (IASP91)
  522. # ============================
  523. # 1. number of data lines of the layered model and selection for including
  524. # the physical dispersion according Kamamori & Anderson (1977)
  525. #-------------------------------------------------------------------------------------------
  526. %(n_model_lines)i %(include_physical_dispersion)i
  527. #-------------------------------------------------------------------------------------------
  528. #
  529. # MULTILAYERED MODEL PARAMETERS (source site)
  530. # ===========================================
  531. # no depth[km] vp[km/s] vs[km/s] ro[g/cm^3] qp qs
  532. #-------------------------------------------------------------------------------------------
  533. %(model_lines)s
  534. #---------------------------------end of all inputs-----------------------------------------
  535. ''' # noqa
  536. return (template % d).encode('ascii')
  537. class QSSPError(gf.store.StoreError):
  538. pass
  539. class Interrupted(gf.store.StoreError):
  540. def __str__(self):
  541. return 'Interrupted.'
  542. class QSSPRunner(object):
  543. def __init__(self, tmp=None, keep_tmp=False):
  544. self.tempdir = mkdtemp(prefix='qssprun-', dir=tmp)
  545. self.keep_tmp = keep_tmp
  546. self.config = None
  547. def run(self, config):
  548. self.config = config
  549. input_fn = pjoin(self.tempdir, 'input')
  550. with open(input_fn, 'wb') as f:
  551. input_str = config.string_for_config()
  552. logger.debug('===== begin qssp input =====\n'
  553. '%s===== end qssp input =====' % input_str.decode())
  554. f.write(input_str)
  555. program = program_bins['qssp.%s' % config.qssp_version]
  556. old_wd = os.getcwd()
  557. os.chdir(self.tempdir)
  558. interrupted = []
  559. def signal_handler(signum, frame):
  560. os.kill(proc.pid, signal.SIGTERM)
  561. interrupted.append(True)
  562. original = signal.signal(signal.SIGINT, signal_handler)
  563. try:
  564. try:
  565. proc = Popen(program, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  566. except OSError:
  567. os.chdir(old_wd)
  568. raise QSSPError(
  569. '''could not start qssp executable: "%s"
  570. Available fomosto backends and download links to the modelling codes are listed
  571. on
  572. https://pyrocko.org/docs/current/apps/fomosto/backends.html
  573. ''' % program)
  574. (output_str, error_str) = proc.communicate(b'input\n')
  575. finally:
  576. signal.signal(signal.SIGINT, original)
  577. if interrupted:
  578. raise KeyboardInterrupt()
  579. logger.debug('===== begin qssp output =====\n'
  580. '%s===== end qssp output =====' % output_str.decode())
  581. errmess = []
  582. if proc.returncode != 0:
  583. errmess.append(
  584. 'qssp had a non-zero exit state: %i' % proc.returncode)
  585. if error_str:
  586. logger.warn(
  587. 'qssp emitted something via stderr: \n\n%s'
  588. % error_str.decode())
  589. # errmess.append('qssp emitted something via stderr')
  590. if output_str.lower().find(b'error') != -1:
  591. errmess.append("the string 'error' appeared in qssp output")
  592. if errmess:
  593. os.chdir(old_wd)
  594. raise QSSPError('''
  595. ===== begin qssp input =====
  596. %s===== end qssp input =====
  597. ===== begin qssp output =====
  598. %s===== end qssp output =====
  599. ===== begin qssp error =====
  600. %s===== end qssp error =====
  601. %s
  602. qssp has been invoked as "%s"'''.lstrip() % (
  603. input_str.decode(),
  604. output_str.decode(),
  605. error_str.decode(),
  606. '\n'.join(errmess),
  607. program))
  608. self.qssp_output = output_str
  609. self.qssp_error = error_str
  610. os.chdir(old_wd)
  611. def get_traces(self):
  612. fns = self.config.get_output_filenames(self.tempdir)
  613. traces = {}
  614. for comp, fn in zip(self.config.components, fns):
  615. data = num.loadtxt(fn, skiprows=1, dtype=num.float)
  616. nsamples, ntraces = data.shape
  617. ntraces -= 1
  618. deltat = (data[-1, 0] - data[0, 0])/(nsamples-1)
  619. toffset = data[0, 0]
  620. for itrace in range(ntraces):
  621. rec = self.config.receivers[itrace]
  622. tmin = rec.tstart + toffset
  623. tr = trace.Trace(
  624. '', '%04i' % itrace, '', comp,
  625. tmin=tmin, deltat=deltat, ydata=data[:, itrace+1],
  626. meta=dict(distance=rec.distance))
  627. traces[itrace, comp] = tr
  628. if self.config.qssp_version == 'ppeg2017':
  629. for itrace in range(ntraces):
  630. for c in 'nez':
  631. tr_accel = traces[itrace, '_acce_' + c]
  632. tr_gravi = traces[itrace, '_gravitation_' + c]
  633. tr_ag = tr_accel.copy()
  634. tr_ag.ydata -= tr_gravi.ydata
  635. tr_ag.set_codes(channel='ag_' + c)
  636. tr_ag.meta = tr_accel.meta
  637. traces[itrace, 'ag_' + c] = tr_ag
  638. traces = list(traces.values())
  639. traces.sort(key=lambda tr: tr.nslc_id)
  640. return traces
  641. def __del__(self):
  642. if self.tempdir:
  643. if not self.keep_tmp:
  644. shutil.rmtree(self.tempdir)
  645. self.tempdir = None
  646. else:
  647. logger.warn(
  648. 'not removing temporary directory: %s' % self.tempdir)
  649. class QSSPGFBuilder(gf.builder.Builder):
  650. nsteps = 2
  651. def __init__(self, store_dir, step, shared, block_size=None, tmp=None,
  652. force=False):
  653. self.store = gf.store.Store(store_dir, 'w')
  654. baseconf = self.store.get_extra('qssp')
  655. if baseconf.qssp_version == '2017':
  656. self.gfmapping = [
  657. (MomentTensor(m=symmat6(1, 0, 0, 1, 0, 0)),
  658. {'_rota_n': (0, -1), '_rota_e': (3, -1), '_rota_z': (5, -1)}),
  659. (MomentTensor(m=symmat6(0, 0, 0, 0, 1, 1)),
  660. {'_rota_n': (1, -1), '_rota_e': (4, -1), '_rota_z': (6, -1)}),
  661. (MomentTensor(m=symmat6(0, 0, 1, 0, 0, 0)),
  662. {'_rota_n': (2, -1), '_rota_z': (7, -1)}),
  663. (MomentTensor(m=symmat6(0, 1, 0, 0, 0, 0)),
  664. {'_rota_n': (8, -1), '_rota_z': (9, -1)}),
  665. ]
  666. elif baseconf.qssp_version == 'ppeg2017':
  667. self.gfmapping = [
  668. (MomentTensor(m=symmat6(1, 0, 0, 1, 0, 0)),
  669. {'ag_n': (0, -1), 'ag_e': (3, -1), 'ag_z': (5, -1)}),
  670. (MomentTensor(m=symmat6(0, 0, 0, 0, 1, 1)),
  671. {'ag_n': (1, -1), 'ag_e': (4, -1), 'ag_z': (6, -1)}),
  672. (MomentTensor(m=symmat6(0, 0, 1, 0, 0, 0)),
  673. {'ag_n': (2, -1), 'ag_z': (7, -1)}),
  674. (MomentTensor(m=symmat6(0, 1, 0, 0, 0, 0)),
  675. {'ag_n': (8, -1), 'ag_z': (9, -1)}),
  676. ]
  677. else:
  678. self.gfmapping = [
  679. (MomentTensor(m=symmat6(1, 0, 0, 1, 0, 0)),
  680. {'un': (0, -1), 'ue': (3, -1), 'uz': (5, -1)}),
  681. (MomentTensor(m=symmat6(0, 0, 0, 0, 1, 1)),
  682. {'un': (1, -1), 'ue': (4, -1), 'uz': (6, -1)}),
  683. (MomentTensor(m=symmat6(0, 0, 1, 0, 0, 0)),
  684. {'un': (2, -1), 'uz': (7, -1)}),
  685. (MomentTensor(m=symmat6(0, 1, 0, 0, 0, 0)),
  686. {'un': (8, -1), 'uz': (9, -1)}),
  687. ]
  688. if step == 0:
  689. block_size = (1, 1, self.store.config.ndistances)
  690. else:
  691. if block_size is None:
  692. block_size = (1, 1, 51)
  693. if len(self.store.config.ns) == 2:
  694. block_size = block_size[1:]
  695. gf.builder.Builder.__init__(
  696. self, self.store.config, step, block_size=block_size, force=force)
  697. conf = QSSPConfigFull(**baseconf.items())
  698. conf.gf_directory = pjoin(store_dir, 'qssp_green')
  699. conf.earthmodel_1d = self.store.config.earthmodel_1d
  700. deltat = self.store.config.deltat
  701. if 'time_window' not in shared:
  702. d = self.store.make_timing_params(
  703. conf.time_region[0], conf.time_region[1],
  704. force=force)
  705. tmax = math.ceil(d['tmax'] / deltat) * deltat
  706. tmin = math.floor(d['tmin'] / deltat) * deltat
  707. shared['time_window'] = tmax - tmin
  708. shared['tstart'] = tmin
  709. self.tstart = shared['tstart']
  710. conf.time_window = shared['time_window']
  711. self.tmp = tmp
  712. if self.tmp is not None:
  713. util.ensuredir(self.tmp)
  714. util.ensuredir(conf.gf_directory)
  715. self.qssp_config = conf
  716. def work_block(self, iblock):
  717. if len(self.store.config.ns) == 2:
  718. (sz, firstx), (sz, lastx), (ns, nx) = \
  719. self.get_block_extents(iblock)
  720. rz = self.store.config.receiver_depth
  721. else:
  722. (rz, sz, firstx), (rz, sz, lastx), (nr, ns, nx) = \
  723. self.get_block_extents(iblock)
  724. gf_filename = 'GF_%gkm_%gkm' % (sz/km, rz/km)
  725. conf = copy.deepcopy(self.qssp_config)
  726. gf_path = os.path.join(conf.gf_directory, '?_' + gf_filename)
  727. if self.step == 0 and len(glob.glob(gf_path)) > 0:
  728. logger.info(
  729. 'Skipping step %i / %i, block %i / %i (GF already exists)'
  730. % (self.step+1, self.nsteps, iblock+1, self.nblocks))
  731. return
  732. logger.info(
  733. 'Starting step %i / %i, block %i / %i' %
  734. (self.step+1, self.nsteps, iblock+1, self.nblocks))
  735. tbeg = time.time()
  736. runner = QSSPRunner(tmp=self.tmp)
  737. conf.receiver_depth = rz/km
  738. conf.sampling_interval = 1.0 / self.gf_config.sample_rate
  739. dx = self.gf_config.distance_delta
  740. if self.step == 0:
  741. distances = [firstx]
  742. else:
  743. distances = num.linspace(firstx, firstx + (nx-1)*dx, nx)
  744. conf.receivers = [
  745. QSSPReceiver(
  746. lat=90-d*cake.m2d,
  747. lon=180.,
  748. tstart=self.tstart,
  749. distance=d)
  750. for d in distances]
  751. if self.step == 0:
  752. gf_filename = 'TEMP' + gf_filename[2:]
  753. gfs = [QSSPGreen(
  754. filename=gf_filename,
  755. depth=sz/km,
  756. calculate=(self.step == 0))]
  757. conf.greens_functions = gfs
  758. trise = 0.001*conf.sampling_interval # make it short (delta impulse)
  759. if self.step == 0:
  760. conf.sources = [QSSPSourceMT(
  761. lat=90-0.001*dx*cake.m2d,
  762. lon=0.0,
  763. trise=trise,
  764. torigin=0.0)]
  765. runner.run(conf)
  766. gf_path = os.path.join(conf.gf_directory, '?_' + gf_filename)
  767. for s in glob.glob(gf_path):
  768. d = s.replace('TEMP_', 'GF_')
  769. os.rename(s, d)
  770. else:
  771. for mt, gfmap in self.gfmapping[
  772. :[3, 4][self.gf_config.ncomponents == 10]]:
  773. m = mt.m_up_south_east()
  774. conf.sources = [QSSPSourceMT(
  775. lat=90-0.001*dx*cake.m2d,
  776. lon=0.0,
  777. mrr=m[0, 0], mtt=m[1, 1], mpp=m[2, 2],
  778. mrt=m[0, 1], mrp=m[0, 2], mtp=m[1, 2],
  779. trise=trise,
  780. torigin=0.0)]
  781. runner.run(conf)
  782. rawtraces = runner.get_traces()
  783. interrupted = []
  784. def signal_handler(signum, frame):
  785. interrupted.append(True)
  786. original = signal.signal(signal.SIGINT, signal_handler)
  787. self.store.lock()
  788. duplicate_inserts = 0
  789. try:
  790. for itr, tr in enumerate(rawtraces):
  791. if tr.channel in gfmap:
  792. x = tr.meta['distance']
  793. ig, factor = gfmap[tr.channel]
  794. if len(self.store.config.ns) == 2:
  795. args = (sz, x, ig)
  796. else:
  797. args = (rz, sz, x, ig)
  798. if conf.cut:
  799. tmin = self.store.t(conf.cut[0], args[:-1])
  800. tmax = self.store.t(conf.cut[1], args[:-1])
  801. if None in (tmin, tmax):
  802. continue
  803. tr.chop(tmin, tmax)
  804. tmin = tr.tmin
  805. tmax = tr.tmax
  806. if conf.fade:
  807. ta, tb, tc, td = [
  808. self.store.t(v, args[:-1])
  809. for v in conf.fade]
  810. if None in (ta, tb, tc, td):
  811. continue
  812. if not (ta <= tb and tb <= tc and tc <= td):
  813. raise QSSPError(
  814. 'invalid fade configuration')
  815. t = tr.get_xdata()
  816. fin = num.interp(t, [ta, tb], [0., 1.])
  817. fout = num.interp(t, [tc, td], [1., 0.])
  818. anti_fin = 1. - fin
  819. anti_fout = 1. - fout
  820. y = tr.ydata
  821. sum_anti_fin = num.sum(anti_fin)
  822. sum_anti_fout = num.sum(anti_fout)
  823. if conf.nonzero_fade_in \
  824. and sum_anti_fin != 0.0:
  825. yin = num.sum(anti_fin*y) / sum_anti_fin
  826. else:
  827. yin = 0.0
  828. if conf.nonzero_fade_out \
  829. and sum_anti_fout != 0.0:
  830. yout = num.sum(anti_fout*y) / sum_anti_fout
  831. else:
  832. yout = 0.0
  833. y2 = anti_fin*yin + fin*fout*y + anti_fout*yout
  834. if conf.relevel_with_fade_in:
  835. y2 -= yin
  836. tr.set_ydata(y2)
  837. gf_tr = gf.store.GFTrace.from_trace(tr)
  838. gf_tr.data *= factor
  839. try:
  840. self.store.put(args, gf_tr)
  841. except gf.store.DuplicateInsert:
  842. duplicate_inserts += 1
  843. finally:
  844. if duplicate_inserts:
  845. logger.warn(
  846. '%i insertions skipped (duplicates)'
  847. % duplicate_inserts)
  848. self.store.unlock()
  849. signal.signal(signal.SIGINT, original)
  850. if interrupted:
  851. raise KeyboardInterrupt()
  852. tend = time.time()
  853. logger.info(
  854. 'Done with step %i / %i, block %i / %i, wallclock time: %.0f s' % (
  855. self.step+1, self.nsteps, iblock+1, self.nblocks, tend-tbeg))
  856. km = 1000.
  857. def init(store_dir, variant):
  858. if variant is None:
  859. variant = '2010beta'
  860. if ('qssp.' + variant) not in program_bins:
  861. raise gf.store.StoreError('unsupported qssp variant: %s' % variant)
  862. qssp = QSSPConfig(qssp_version=variant)
  863. if variant != 'ppeg2017':
  864. qssp.time_region = (
  865. gf.Timing('begin-50'),
  866. gf.Timing('end+100'))
  867. qssp.cut = (
  868. gf.Timing('begin-50'),
  869. gf.Timing('end+100'))
  870. else: # variant == 'ppeg2017':
  871. qssp.frequency_max = 0.5
  872. qssp.time_region = [
  873. gf.Timing('-100'), gf.Timing('{stored:begin}+100')]
  874. qssp.cut = [
  875. gf.Timing('-100'), gf.Timing('{stored:begin}+100')]
  876. qssp.antialiasing_factor = 1.0e-10
  877. qssp.toroidal_modes = False
  878. qssp.cutoff_harmonic_degree_min = 2500
  879. qssp.cutoff_harmonic_degree_max = 2500
  880. qssp.crit_frequency_sge = 5.0
  881. qssp.crit_harmonic_degree_sge = 50000
  882. qssp.source_patch_radius = 10.0
  883. qssp.bandpass_order = 6
  884. qssp.bandpass_corner_low = 0.0
  885. qssp.bandpass_corner_high = 0.125
  886. store_id = os.path.basename(os.path.realpath(store_dir))
  887. if variant == 'ppeg2017':
  888. quantity = 'acceleration'
  889. else:
  890. quantity = None
  891. if variant == 'ppeg2017':
  892. sample_rate = 4.0
  893. else:
  894. sample_rate = 0.2
  895. config = gf.meta.ConfigTypeA(
  896. id=store_id,
  897. ncomponents=10,
  898. component_scheme='elastic10',
  899. stored_quantity=quantity,
  900. sample_rate=sample_rate,
  901. receiver_depth=0*km,
  902. source_depth_min=10*km,
  903. source_depth_max=20*km,
  904. source_depth_delta=10*km,
  905. distance_min=100*km,
  906. distance_max=1000*km,
  907. distance_delta=10*km,
  908. earthmodel_1d=cake.load_model(),
  909. modelling_code_id='qssp',
  910. tabulated_phases=[
  911. gf.meta.TPDef(
  912. id='begin',
  913. definition='p,P,p\\,P\\,Pv_(cmb)p'),
  914. gf.meta.TPDef(
  915. id='end',
  916. definition='2.5'),
  917. gf.meta.TPDef(
  918. id='P',
  919. definition='!P'),
  920. gf.meta.TPDef(
  921. id='S',
  922. definition='!S'),
  923. gf.meta.TPDef(
  924. id='p',
  925. definition='!p'),
  926. gf.meta.TPDef(
  927. id='s',
  928. definition='!s')])
  929. config.validate()
  930. return gf.store.Store.create_editables(
  931. store_dir,
  932. config=config,
  933. extra={'qssp': qssp})
  934. def build(
  935. store_dir,
  936. force=False,
  937. nworkers=None,
  938. continue_=False,
  939. step=None,
  940. iblock=None):
  941. return QSSPGFBuilder.build(
  942. store_dir, force=force, nworkers=nworkers, continue_=continue_,
  943. step=step, iblock=iblock)