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

# http://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------
from __future__ import absolute_import, division
import numpy as num
import logging
import os
import shutil
import glob
import copy
import signal
import math
import time
from tempfile import mkdtemp
from subprocess import Popen, PIPE
from os.path import join as pjoin
from pyrocko import trace, util, cake, gf
from pyrocko.guts import Object, Float, String, Bool, Tuple, Int, List
from pyrocko.moment_tensor import MomentTensor, symmat6
guts_prefix = 'pf'
logger = logging.getLogger('pyrocko.fomosto.qssp')
# how to call the programs
program_bins = {
'qssp.2010beta': 'fomosto_qssp2010beta',
'qssp.2010': 'fomosto_qssp2010',
'qssp.2017': 'fomosto_qssp2017',
'qssp.ppeg2017': 'fomosto_qsspppeg2017',
}
def have_backend():
have_any = False
for cmd in [[exe] for exe in program_bins.values()]:
try:
p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
(stdout, stderr) = p.communicate()
have_any = True
except OSError:
pass
return have_any
qssp_components = {
1: 'ae an az gr sd te tn ue un uz ve vn vz'.split(),
2: 'ar at ap gr sd tt tp ur ut up vr vt vp'.split(),
3: '_rota_e _rota_n _rota_z'.split(),
4: '_gravitation_e _gravitation_n _gravitation_z '
'_acce_e _acce_n _acce_z'.split()
}
def str_float_vals(vals):
return ' '.join(['%.6e' % val for val in vals])
def cake_model_to_config(mod):
k = 1000.
srows = []
for i, row in enumerate(mod.to_scanlines()):
depth, vp, vs, rho, qp, qs = row
row = [depth/k, vp/k, vs/k, rho/k, qp, qs]
srows.append('%i %s' % (i+1, str_float_vals(row)))
return '\n'.join(srows), len(srows)
class QSSPSource(Object):
lat = Float.T(default=0.0)
lon = Float.T(default=0.0)
depth = Float.T(default=10.0)
torigin = Float.T(default=0.0)
trise = Float.T(default=1.0)
def string_for_config(self):
return '%(lat)15e %(lon)15e %(depth)15e %(torigin)15e %(trise)15e' \
% self.__dict__
class QSSPSourceMT(QSSPSource):
munit = Float.T(default=1.0)
mrr = Float.T(default=1.0)
mtt = Float.T(default=1.0)
mpp = Float.T(default=1.0)
mrt = Float.T(default=0.0)
mrp = Float.T(default=0.0)
mtp = Float.T(default=0.0)
def string_for_config(self):
return '%(munit)15e %(mrr)15e %(mtt)15e %(mpp)15e ' \
'%(mrt)15e %(mrp)15e %(mtp)15e ' \
% self.__dict__ + QSSPSource.string_for_config(self)
class QSSPSourceDC(QSSPSource):
moment = Float.T(default=1.0e9)
strike = Float.T(default=0.0)
dip = Float.T(default=90.0)
rake = Float.T(default=0.0)
def string_for_config(self):
return '%(moment)15e %(strike)15e %(dip)15e %(rake)15e ' \
% self.__dict__ + QSSPSource.string_for_config(self)
class QSSPReceiver(Object):
lat = Float.T(default=10.0)
lon = Float.T(default=0.0)
name = String.T(default='')
tstart = Float.T(default=0.0)
distance = Float.T(default=0.0)
def string_for_config(self):
return "%(lat)15e %(lon)15e '%(name)s' %(tstart)e" % self.__dict__
class QSSPGreen(Object):
depth = Float.T(default=10.0)
filename = String.T(default='GF_10km')
calculate = Bool.T(default=True)
def string_for_config(self):
return "%(depth)15e '%(filename)s' %(calculate)i" % self.__dict__
class QSSPConfig(Object):
qssp_version = String.T(default='2010beta')
time_region = Tuple.T(2, gf.Timing.T(), default=(
gf.Timing('-10'), gf.Timing('+890')))
frequency_max = Float.T(optional=True)
slowness_max = Float.T(default=0.4)
antialiasing_factor = Float.T(default=0.1)
# only available in 2017:
switch_turning_point_filter = Int.T(default=0)
max_pene_d1 = Float.T(default=2891.5)
max_pene_d2 = Float.T(default=6371.0)
earth_radius = Float.T(default=6371.0)
switch_free_surf_reflection = Int.T(default=1)
lowpass_order = Int.T(default=0, optional=True)
lowpass_corner = Float.T(default=1.0, optional=True)
bandpass_order = Int.T(default=0, optional=True)
bandpass_corner_low = Float.T(default=1.0, optional=True)
bandpass_corner_high = Float.T(default=1.0, optional=True)
output_slowness_min = Float.T(default=0.0, optional=True)
output_slowness_max = Float.T(optional=True)
spheroidal_modes = Bool.T(default=True)
toroidal_modes = Bool.T(default=True)
# only available in 2010beta:
cutoff_harmonic_degree_sd = Int.T(optional=True, default=0)
cutoff_harmonic_degree_min = Int.T(default=0)
cutoff_harmonic_degree_max = Int.T(default=25000)
crit_frequency_sge = Float.T(default=0.0)
crit_harmonic_degree_sge = Int.T(default=0)
include_physical_dispersion = Bool.T(default=False)
source_patch_radius = Float.T(default=0.0)
cut = Tuple.T(2, gf.Timing.T(), optional=True)
fade = Tuple.T(4, gf.Timing.T(), optional=True)
relevel_with_fade_in = Bool.T(default=False)
nonzero_fade_in = Bool.T(default=False)
nonzero_fade_out = Bool.T(default=False)
def items(self):
return dict(self.T.inamevals(self))
class QSSPConfigFull(QSSPConfig):
time_window = Float.T(default=900.0)
receiver_depth = Float.T(default=0.0)
sampling_interval = Float.T(default=5.0)
output_filename = String.T(default='receivers')
output_format = Int.T(default=1)
output_time_window = Float.T(optional=True)
gf_directory = String.T(default='qssp_green')
greens_functions = List.T(QSSPGreen.T())
sources = List.T(QSSPSource.T())
receivers = List.T(QSSPReceiver.T())
earthmodel_1d = gf.meta.Earthmodel1D.T(optional=True)
@staticmethod
def example():
conf = QSSPConfigFull()
conf.sources.append(QSSPSourceMT())
lats = [20.]
conf.receivers.extend(QSSPReceiver(lat=lat) for lat in lats)
conf.greens_functions.append(QSSPGreen())
return conf
@property
def components(self):
if self.qssp_version == '2017':
fmt = 3
elif self.qssp_version == 'ppeg2017':
fmt = 4
else:
fmt = self.output_format
return qssp_components[fmt]
def get_output_filenames(self, rundir):
if self.qssp_version in ('2017', 'ppeg2017'):
return [
pjoin(rundir, self.output_filename + c + '.dat')
for c in self.components]
else:
return [
pjoin(rundir, self.output_filename + '.' + c)
for c in self.components]
def ensure_gf_directory(self):
util.ensuredir(self.gf_directory)
def string_for_config(self):
def aggregate(l):
return len(l), '\n'.join(x.string_for_config() for x in l)
assert len(self.greens_functions) > 0
assert len(self.sources) > 0
assert len(self.receivers) > 0
d = self.__dict__.copy()
if self.output_time_window is None:
d['output_time_window'] = self.time_window
if self.output_slowness_max is None:
d['output_slowness_max'] = self.slowness_max
if self.frequency_max is None:
d['frequency_max'] = 0.5/self.sampling_interval
d['gf_directory'] = os.path.abspath(self.gf_directory) + '/'
d['n_receiver_lines'], d['receiver_lines'] = aggregate(self.receivers)
d['n_source_lines'], d['source_lines'] = aggregate(self.sources)
d['n_gf_lines'], d['gf_lines'] = aggregate(self.greens_functions)
model_str, nlines = cake_model_to_config(self.earthmodel_1d)
d['n_model_lines'] = nlines
d['model_lines'] = model_str
if len(self.sources) == 0 or isinstance(self.sources[0], QSSPSourceMT):
d['point_source_type'] = 1
else:
d['point_source_type'] = 2
if self.qssp_version == '2010beta':
d['scutoff_doc'] = '''
# (SH waves), and cutoff harmonic degree for static deformation
'''.strip()
d['scutoff'] = '%i' % self.cutoff_harmonic_degree_sd
d['sfilter_doc'] = '''
# 3. selection of order of Butterworth low-pass filter (if <= 0, then no
# filtering), corner frequency (smaller than the cut-off frequency defined
# above)
'''.strip()
if self.bandpass_order != 0:
raise QSSPError(
'this version of qssp does not support bandpass '
'settings, use lowpass instead')
d['sfilter'] = '%i %f' % (
self.lowpass_order,
self.lowpass_corner)
elif self.qssp_version in ('2010', '2017', 'ppeg2017'):
d['scutoff_doc'] = '''
# (SH waves), minimum and maximum cutoff harmonic degrees
# Note: if the near-field static displacement is desired, the minimum
# cutoff harmonic degree should not be smaller than, e.g., 2000.
'''.strip()
d['scutoff'] = '%i %i' % (
self.cutoff_harmonic_degree_min,
self.cutoff_harmonic_degree_max)
d['sfilter_doc'] = '''
# 3. selection of order of Butterworth bandpass filter (if <= 0, then no
# filtering), lower and upper corner frequencies (smaller than the cut-off
# frequency defined above)
'''.strip()
if self.lowpass_order != 0:
raise QSSPError(
'this version of qssp does not support lowpass settings, '
'use bandpass instead')
d['sfilter'] = '%i %f %f' % (
self.bandpass_order,
self.bandpass_corner_low,
self.bandpass_corner_high)
if self.qssp_version in ('2017', 'ppeg2017'):
template = '''# autogenerated QSSP input by qssp.py
#
# This is the input file of FORTRAN77 program "qssp2017" for calculating
# synthetic seismograms of a self-gravitating, spherically symmetric,
# isotropic and viscoelastic earth.
#
# by
# Rongjiang Wang <wang@gfz-potsdam.de>
# Helmholtz-Centre Potsdam
# GFZ German Reseach Centre for Geosciences
# Telegrafenberg, D-14473 Potsdam, Germany
#
# Last modified: Potsdam, October 2017
#
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# If not specified, SI Unit System is used overall!
#
# Coordinate systems:
# spherical (r,t,p) with r = radial,
# t = co-latitude,
# p = east longitude.
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
#
# UNIFORM RECEIVER DEPTH
# ======================
# 1. uniform receiver depth [km]
#-------------------------------------------------------------------------------------------
%(receiver_depth)e
#-------------------------------------------------------------------------------------------
#
# SPACE-TIME SAMPLING PARAMETERS
# =========================
# 1. time window [sec], sampling interval [sec]
# 2. max. frequency [Hz] of Green's functions
# 3. max. slowness [s/km] of Green's functions
# Note: if the near-field static displacement is desired, the maximum slowness should not
# be smaller than the S wave slowness in the receiver layer
# 4. anti-aliasing factor (> 0 & < 1), if it is <= 0 or >= 1/e (~ 0.4), then
# default value of 1/e is used (e.g., 0.1 = alias phases will be suppressed
# to 10%% of their original amplitude)
# 5. switch (1/0 = yes/no) of turning-point filter, the range (d1, d2) of max. penetration
# depth [km] (d1 is meaningless if it is smaller than the receiver/source depth, and
# d2 is meaningless if it is equal to or larger than the earth radius)
#
# Note: The turning-point filter (Line 5) works only for the extended QSSP code (e.g.,
# qssp2016). if this filter is selected, all phases with the turning point
# shallower than d1 or deeper than d2 will be filtered.
#
# 6. Earth radius [km], switch of free-surface-reflection filter (1/0 = with/without free
# surface reflection)
#
# Note: The free-surface-reflection filter (Line 6) works only for the extended QSSP
# code (e.g., qssp2016). if this filter is selected, all phases with the turning
# point shallower than d1 or deeper than d2 will be filtered.
#-------------------------------------------------------------------------------------------
%(time_window)e %(sampling_interval)e
%(frequency_max)e
%(slowness_max)e
%(antialiasing_factor)e
%(switch_turning_point_filter)i %(max_pene_d1)e %(max_pene_d2)e
%(earth_radius)e %(switch_free_surf_reflection)i
#-------------------------------------------------------------------------------------------
#
# SELF-GRAVITATING EFFECT
# =======================
# 1. the critical frequency [Hz] and the critical harmonic degree, below which
# the self-gravitating effect should be included
#-------------------------------------------------------------------------------------------
%(crit_frequency_sge)e %(crit_harmonic_degree_sge)i
#-------------------------------------------------------------------------------------------
#
# WAVE TYPES
# ==========
# 1. selection (1/0 = yes/no) of speroidal modes (P-SV waves), selection of toroidal modes
%(scutoff_doc)s
#-------------------------------------------------------------------------------------------
%(spheroidal_modes)i %(toroidal_modes)i %(scutoff)s
#-------------------------------------------------------------------------------------------
# GREEN'S FUNCTION FILES
# ======================
# 1. number of discrete source depths, estimated radius of each source patch [km] and
# directory for Green's functions
# 2. list of the source depths [km], the respective file names of the Green's
# functions (spectra) and the switch number (0/1) (0 = do not calculate
# this Green's function because it exists already, 1 = calculate or update
# this Green's function. Note: update is required if any of the above
# parameters is changed)
#-------------------------------------------------------------------------------------------
%(n_gf_lines)i %(source_patch_radius)e '%(gf_directory)s'
%(gf_lines)s
#--------------------------------------------------------------------------------------------------------
#
# MULTI-EVENT SOURCE PARAMETERS
# =============================
# 1. number of discrete point sources and selection of the source data format
# (1, 2 or 3)
# 2. list of the multi-event sources
#
# Format 1 (full moment tensor):
# Unit Mrr Mtt Mpp Mrt Mrp Mtp Lat Lon Depth T_origin T_rise
# [Nm] [deg] [deg] [km] [sec] [sec]
#
# Format 2 (double couple):
# Unit Strike Dip Rake Lat Lon Depth T_origin T_rise
# [Nm] [deg] [deg] [deg] [deg] [deg] [km] [sec] [sec]
#
# Format 3 (single force):
# Unit Feast Fnorth Fvertical Lat Lon Depth T_origin T_rise
# [N] [deg] [deg] [km] [sec] [sec]
#
# Note: for each point source, the default moment (force) rate time function is used, defined by a
# squared half-period (T_rise) sinusoid starting at T_origin.
#-----------------------------------------------------------------------------------
%(n_source_lines)i %(point_source_type)i
%(source_lines)s
#--------------------------------------------------------------------------------------------------------
#
# RECEIVER PARAMETERS
# ===================
# 1. select output observables (1/0 = yes/no)
# Note: the gravity change defined here is space based, i.e., the effect due to free-air
# gradient and inertial are not included. the vertical component is positve upwards.
# 2. output file name
# 3. output time window [sec] (<= Green's function time window)
%(sfilter_doc)s
# 5. lower and upper slowness cut-off [s/km] (slowness band-pass filter)
# 6. number of receiver
# 7. list of the station parameters
# Format:
# Lat Lon Name Time_reduction
# [deg] [deg] [sec]
# (Note: Time_reduction = start time of the time window)
#---------------------------------------------------------------------------------------------------------------
# disp | velo | acce | strain | strain_rate | stress | stress_rate | rotation | rot_rate | gravitation | gravity
#---------------------------------------------------------------------------------------------------------------
# 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 1 0 0 0
'%(output_filename)s'
%(output_time_window)e
%(sfilter)s
%(output_slowness_min)e %(output_slowness_max)e
%(n_receiver_lines)i
%(receiver_lines)s
#-------------------------------------------------------------------------------------------
#
# LAYERED EARTH MODEL (IASP91)
# ============================
# 1. number of data lines of the layered model and selection for including
# the physical dispersion according Kamamori & Anderson (1977)
#-------------------------------------------------------------------------------------------
%(n_model_lines)i %(include_physical_dispersion)i
#--------------------------------------------------------------------------------------------------------
#
# MODEL PARAMETERS
# ================
# no depth[km] vp[km/s] vs[km/s] ro[g/cm^3] qp qs
#-------------------------------------------------------------------------------------------
%(model_lines)s
#---------------------------------end of all inputs-----------------------------------------
''' # noqa
else:
template = '''# autogenerated QSSP input by qssp.py
#
# This is the input file of FORTRAN77 program "qssp2010" for calculating
# synthetic seismograms of a self-gravitating, spherically symmetric,
# isotropic and viscoelastic earth.
#
# by
# Rongjiang Wang <wang@gfz-potsdam.de>
# Helmholtz-Centre Potsdam
# GFZ German Reseach Centre for Geosciences
# Telegrafenberg, D-14473 Potsdam, Germany
#
# Last modified: Potsdam, July, 2010
#
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# If not specified, SI Unit System is used overall!
#
# Coordinate systems:
# spherical (r,t,p) with r = radial,
# t = co-latitude,
# p = east longitude.
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
#
# UNIFORM RECEIVER DEPTH
# ======================
# 1. uniform receiver depth [km]
#-------------------------------------------------------------------------------------------
%(receiver_depth)e
#-------------------------------------------------------------------------------------------
#
# TIME (FREQUENCY) SAMPLING
# =========================
# 1. time window [sec], sampling interval [sec]
# 2. max. frequency [Hz] of Green's functions
# 3. max. slowness [s/km] of Green's functions
# Note: if the near-field static displacement is desired, the maximum slowness should not
# be smaller than the S wave slowness in the receiver layer
# 4. anti-aliasing factor (> 0 & < 1), if it is <= 0 or >= 1/e (~ 0.4), then
# default value of 1/e is used (e.g., 0.1 = alias phases will be suppressed
# to 10%% of their original amplitude)
#
# Note: The computation effort increases linearly the time window and
# quadratically with the cut-off frequency.
#-------------------------------------------------------------------------------------------
%(time_window)e %(sampling_interval)e
%(frequency_max)e
%(slowness_max)e
%(antialiasing_factor)e
#-------------------------------------------------------------------------------------------
#
# SELF-GRAVITATING EFFECT
# =======================
# 1. the critical frequency [Hz] and the critical harmonic degree, below which
# the self-gravitating effect should be included
#-------------------------------------------------------------------------------------------
%(crit_frequency_sge)e %(crit_harmonic_degree_sge)i
#-------------------------------------------------------------------------------------------
#
# WAVE TYPES
# ==========
# 1. selection (1/0 = yes/no) of speroidal modes (P-SV waves), selection of toroidal modes
%(scutoff_doc)s
#-------------------------------------------------------------------------------------------
%(spheroidal_modes)i %(toroidal_modes)i %(scutoff)s
#-------------------------------------------------------------------------------------------
# GREEN'S FUNCTION FILES
# ======================
# 1. number of discrete source depths, estimated radius of each source patch [km] and
# directory for Green's functions
# 2. list of the source depths [km], the respective file names of the Green's
# functions (spectra) and the switch number (0/1) (0 = do not calculate
# this Green's function because it exists already, 1 = calculate or update
# this Green's function. Note: update is required if any of the above
# parameters is changed)
#-------------------------------------------------------------------------------------------
%(n_gf_lines)i %(source_patch_radius)e '%(gf_directory)s'
%(gf_lines)s
#-------------------------------------------------------------------------------------------
#
# MULTI-EVENT SOURCE PARAMETERS
# =============================
# 1. number of discrete point sources and selection of the source data format
# (1 or 2)
# 2. list of the multi-event sources
# Format 1:
# M-Unit Mrr Mtt Mpp Mrt Mrp Mtp Lat Lon Depth T_origin T_rise
# [Nm] [deg] [deg] [km] [sec] [sec]
# Format 2:
# Moment Strike Dip Rake Lat Lon Depth T_origin T_rise
# [Nm] [deg] [deg] [deg] [deg] [deg] [km] [sec] [sec]
#-------------------------------------------------------------------------------------------
%(n_source_lines)i %(point_source_type)i
%(source_lines)s
#-------------------------------------------------------------------------------------------
#
# RECEIVER PARAMETERS
# ===================
# 1. output file name and and selection of output format:
# 1 = cartesian: vertical(z)/north(n)/east(e);
# 2 = spherical: radial(r)/theta(t)/phi(p)
# (Note: if output format 2 is selected, the epicenter (T_origin = 0)
# 2. output time window [sec] (<= Green's function time window)
%(sfilter_doc)s
# 4. lower and upper slowness cut-off [s/km] (slowness band-pass filter)
# 5. number of receiver
# 6. list of the station parameters
# Format:
# Lat Lon Name Time_reduction
# [deg] [deg] [sec]
# (Note: Time_reduction = start time of the time window)
#-------------------------------------------------------------------------------------------
'%(output_filename)s' %(output_format)i
%(output_time_window)e
%(sfilter)s
%(output_slowness_min)e %(output_slowness_max)e
%(n_receiver_lines)i
%(receiver_lines)s
#-------------------------------------------------------------------------------------------
#
# LAYERED EARTH MODEL (IASP91)
# ============================
# 1. number of data lines of the layered model and selection for including
# the physical dispersion according Kamamori & Anderson (1977)
#-------------------------------------------------------------------------------------------
%(n_model_lines)i %(include_physical_dispersion)i
#-------------------------------------------------------------------------------------------
#
# MULTILAYERED MODEL PARAMETERS (source site)
# ===========================================
# no depth[km] vp[km/s] vs[km/s] ro[g/cm^3] qp qs
#-------------------------------------------------------------------------------------------
%(model_lines)s
#---------------------------------end of all inputs-----------------------------------------
''' # noqa
return (template % d).encode('ascii')
class QSSPError(gf.store.StoreError):
pass
class Interrupted(gf.store.StoreError):
def __str__(self):
return 'Interrupted.'
class QSSPRunner(object):
def __init__(self, tmp=None, keep_tmp=False):
self.tempdir = mkdtemp(prefix='qssprun-', dir=tmp)
self.keep_tmp = keep_tmp
self.config = None
def run(self, config):
self.config = config
input_fn = pjoin(self.tempdir, 'input')
with open(input_fn, 'wb') as f:
input_str = config.string_for_config()
logger.debug('===== begin qssp input =====\n'
'%s===== end qssp input =====' % input_str.decode())
f.write(input_str)
program = program_bins['qssp.%s' % config.qssp_version]
old_wd = os.getcwd()
os.chdir(self.tempdir)
interrupted = []
def signal_handler(signum, frame):
os.kill(proc.pid, signal.SIGTERM)
interrupted.append(True)
original = signal.signal(signal.SIGINT, signal_handler)
try:
try:
proc = Popen(program, stdin=PIPE, stdout=PIPE, stderr=PIPE)
except OSError:
os.chdir(old_wd)
raise QSSPError(
'''could not start qssp executable: "%s"
Available fomosto backends and download links to the modelling codes are listed
on
https://pyrocko.org/docs/current/apps/fomosto/backends.html
''' % program)
(output_str, error_str) = proc.communicate(b'input\n')
finally:
signal.signal(signal.SIGINT, original)
if interrupted:
raise KeyboardInterrupt()
logger.debug('===== begin qssp output =====\n'
'%s===== end qssp output =====' % output_str.decode())
errmess = []
if proc.returncode != 0:
errmess.append(
'qssp had a non-zero exit state: %i' % proc.returncode)
if error_str:
logger.warn(
'qssp emitted something via stderr: \n\n%s'
% error_str.decode())
# errmess.append('qssp emitted something via stderr')
if output_str.lower().find(b'error') != -1:
errmess.append("the string 'error' appeared in qssp output")
if errmess:
os.chdir(old_wd)
raise QSSPError('''
===== begin qssp input =====
%s===== end qssp input =====
===== begin qssp output =====
%s===== end qssp output =====
===== begin qssp error =====
%s===== end qssp error =====
%s
qssp has been invoked as "%s"'''.lstrip() % (
input_str.decode(),
output_str.decode(),
error_str.decode(),
'\n'.join(errmess),
program))
self.qssp_output = output_str
self.qssp_error = error_str
os.chdir(old_wd)
def get_traces(self):
fns = self.config.get_output_filenames(self.tempdir)
traces = {}
for comp, fn in zip(self.config.components, fns):
data = num.loadtxt(fn, skiprows=1, dtype=num.float)
nsamples, ntraces = data.shape
ntraces -= 1
deltat = (data[-1, 0] - data[0, 0])/(nsamples-1)
toffset = data[0, 0]
for itrace in range(ntraces):
rec = self.config.receivers[itrace]
tmin = rec.tstart + toffset
tr = trace.Trace(
'', '%04i' % itrace, '', comp,
tmin=tmin, deltat=deltat, ydata=data[:, itrace+1],
meta=dict(distance=rec.distance))
traces[itrace, comp] = tr
if self.config.qssp_version == 'ppeg2017':
for itrace in range(ntraces):
for c in 'nez':
tr_accel = traces[itrace, '_acce_' + c]
tr_gravi = traces[itrace, '_gravitation_' + c]
tr_ag = tr_accel.copy()
tr_ag.ydata -= tr_gravi.ydata
tr_ag.set_codes(channel='ag_' + c)
tr_ag.meta = tr_accel.meta
traces[itrace, 'ag_' + c] = tr_ag
traces = list(traces.values())
traces.sort(key=lambda tr: tr.nslc_id)
return traces
def __del__(self):
if self.tempdir:
if not self.keep_tmp:
shutil.rmtree(self.tempdir)
self.tempdir = None
else:
logger.warn(
'not removing temporary directory: %s' % self.tempdir)
class QSSPGFBuilder(gf.builder.Builder):
nsteps = 2
def __init__(self, store_dir, step, shared, block_size=None, tmp=None,
force=False):
self.store = gf.store.Store(store_dir, 'w')
baseconf = self.store.get_extra('qssp')
if baseconf.qssp_version == '2017':
self.gfmapping = [
(MomentTensor(m=symmat6(1, 0, 0, 1, 0, 0)),
{'_rota_n': (0, -1), '_rota_e': (3, -1), '_rota_z': (5, -1)}),
(MomentTensor(m=symmat6(0, 0, 0, 0, 1, 1)),
{'_rota_n': (1, -1), '_rota_e': (4, -1), '_rota_z': (6, -1)}),
(MomentTensor(m=symmat6(0, 0, 1, 0, 0, 0)),
{'_rota_n': (2, -1), '_rota_z': (7, -1)}),
(MomentTensor(m=symmat6(0, 1, 0, 0, 0, 0)),
{'_rota_n': (8, -1), '_rota_z': (9, -1)}),
]
elif baseconf.qssp_version == 'ppeg2017':
self.gfmapping = [
(MomentTensor(m=symmat6(1, 0, 0, 1, 0, 0)),
{'ag_n': (0, -1), 'ag_e': (3, -1), 'ag_z': (5, -1)}),
(MomentTensor(m=symmat6(0, 0, 0, 0, 1, 1)),
{'ag_n': (1, -1), 'ag_e': (4, -1), 'ag_z': (6, -1)}),
(MomentTensor(m=symmat6(0, 0, 1, 0, 0, 0)),
{'ag_n': (2, -1), 'ag_z': (7, -1)}),
(MomentTensor(m=symmat6(0, 1, 0, 0, 0, 0)),
{'ag_n': (8, -1), 'ag_z': (9, -1)}),
]
else:
self.gfmapping = [
(MomentTensor(m=symmat6(1, 0, 0, 1, 0, 0)),
{'un': (0, -1), 'ue': (3, -1), 'uz': (5, -1)}),
(MomentTensor(m=symmat6(0, 0, 0, 0, 1, 1)),
{'un': (1, -1), 'ue': (4, -1), 'uz': (6, -1)}),
(MomentTensor(m=symmat6(0, 0, 1, 0, 0, 0)),
{'un': (2, -1), 'uz': (7, -1)}),
(MomentTensor(m=symmat6(0, 1, 0, 0, 0, 0)),
{'un': (8, -1), 'uz': (9, -1)}),
]
if step == 0:
block_size = (1, 1, self.store.config.ndistances)
else:
if block_size is None:
block_size = (1, 1, 51)
if len(self.store.config.ns) == 2:
block_size = block_size[1:]
gf.builder.Builder.__init__(
self, self.store.config, step, block_size=block_size, force=force)
conf = QSSPConfigFull(**baseconf.items())
conf.gf_directory = pjoin(store_dir, 'qssp_green')
conf.earthmodel_1d = self.store.config.earthmodel_1d
deltat = self.store.config.deltat
if 'time_window' not in shared:
d = self.store.make_timing_params(
conf.time_region[0], conf.time_region[1],
force=force)
tmax = math.ceil(d['tmax'] / deltat) * deltat
tmin = math.floor(d['tmin'] / deltat) * deltat
shared['time_window'] = tmax - tmin
shared['tstart'] = tmin
self.tstart = shared['tstart']
conf.time_window = shared['time_window']
self.tmp = tmp
if self.tmp is not None:
util.ensuredir(self.tmp)
util.ensuredir(conf.gf_directory)
self.qssp_config = conf
def work_block(self, iblock):
if len(self.store.config.ns) == 2:
(sz, firstx), (sz, lastx), (ns, nx) = \
self.get_block_extents(iblock)
rz = self.store.config.receiver_depth
else:
(rz, sz, firstx), (rz, sz, lastx), (nr, ns, nx) = \
self.get_block_extents(iblock)
gf_filename = 'GF_%gkm_%gkm' % (sz/km, rz/km)
conf = copy.deepcopy(self.qssp_config)
gf_path = os.path.join(conf.gf_directory, '?_' + gf_filename)
if self.step == 0 and len(glob.glob(gf_path)) > 0:
logger.info(
'Skipping step %i / %i, block %i / %i (GF already exists)'
% (self.step+1, self.nsteps, iblock+1, self.nblocks))
return
logger.info(
'Starting step %i / %i, block %i / %i' %
(self.step+1, self.nsteps, iblock+1, self.nblocks))
tbeg = time.time()
runner = QSSPRunner(tmp=self.tmp)
conf.receiver_depth = rz/km
conf.sampling_interval = 1.0 / self.gf_config.sample_rate
dx = self.gf_config.distance_delta
if self.step == 0:
distances = [firstx]
else:
distances = num.linspace(firstx, firstx + (nx-1)*dx, nx)
conf.receivers = [
QSSPReceiver(
lat=90-d*cake.m2d,
lon=180.,
tstart=self.tstart,
distance=d)
for d in distances]
if self.step == 0:
gf_filename = 'TEMP' + gf_filename[2:]
gfs = [QSSPGreen(
filename=gf_filename,
depth=sz/km,
calculate=(self.step == 0))]
conf.greens_functions = gfs
trise = 0.001*conf.sampling_interval # make it short (delta impulse)
if self.step == 0:
conf.sources = [QSSPSourceMT(
lat=90-0.001*dx*cake.m2d,
lon=0.0,
trise=trise,
torigin=0.0)]
runner.run(conf)
gf_path = os.path.join(conf.gf_directory, '?_' + gf_filename)
for s in glob.glob(gf_path):
d = s.replace('TEMP_', 'GF_')
os.rename(s, d)
else:
for mt, gfmap in self.gfmapping[
:[3, 4][self.gf_config.ncomponents == 10]]:
m = mt.m_up_south_east()
conf.sources = [QSSPSourceMT(
lat=90-0.001*dx*cake.m2d,
lon=0.0,
mrr=m[0, 0], mtt=m[1, 1], mpp=m[2, 2],
mrt=m[0, 1], mrp=m[0, 2], mtp=m[1, 2],
trise=trise,
torigin=0.0)]
runner.run(conf)
rawtraces = runner.get_traces()
interrupted = []
def signal_handler(signum, frame):
interrupted.append(True)
original = signal.signal(signal.SIGINT, signal_handler)
self.store.lock()
duplicate_inserts = 0
try:
for itr, tr in enumerate(rawtraces):
if tr.channel in gfmap:
x = tr.meta['distance']
ig, factor = gfmap[tr.channel]
if len(self.store.config.ns) == 2:
args = (sz, x, ig)
else:
args = (rz, sz, x, ig)
if conf.cut:
tmin = self.store.t(conf.cut[0], args[:-1])
tmax = self.store.t(conf.cut[1], args[:-1])
if None in (tmin, tmax):
continue
tr.chop(tmin, tmax)
tmin = tr.tmin
tmax = tr.tmax
if conf.fade:
ta, tb, tc, td = [
self.store.t(v, args[:-1])
for v in conf.fade]
if None in (ta, tb, tc, td):
continue
if not (ta <= tb and tb <= tc and tc <= td):
raise QSSPError(
'invalid fade configuration')
t = tr.get_xdata()
fin = num.interp(t, [ta, tb], [0., 1.])
fout = num.interp(t, [tc, td], [1., 0.])
anti_fin = 1. - fin
anti_fout = 1. - fout
y = tr.ydata
sum_anti_fin = num.sum(anti_fin)
sum_anti_fout = num.sum(anti_fout)
if conf.nonzero_fade_in \
and sum_anti_fin != 0.0:
yin = num.sum(anti_fin*y) / sum_anti_fin
else:
yin = 0.0
if conf.nonzero_fade_out \
and sum_anti_fout != 0.0:
yout = num.sum(anti_fout*y) / sum_anti_fout
else:
yout = 0.0
y2 = anti_fin*yin + fin*fout*y + anti_fout*yout
if conf.relevel_with_fade_in:
y2 -= yin
tr.set_ydata(y2)
gf_tr = gf.store.GFTrace.from_trace(tr)
gf_tr.data *= factor
try:
self.store.put(args, gf_tr)
except gf.store.DuplicateInsert:
duplicate_inserts += 1
finally:
if duplicate_inserts:
logger.warn(
'%i insertions skipped (duplicates)'
% duplicate_inserts)
self.store.unlock()
signal.signal(signal.SIGINT, original)
if interrupted:
raise KeyboardInterrupt()
tend = time.time()
logger.info(
'Done with step %i / %i, block %i / %i, wallclock time: %.0f s' % (
self.step+1, self.nsteps, iblock+1, self.nblocks, tend-tbeg))
km = 1000.
def init(store_dir, variant):
if variant is None:
variant = '2010beta'
if ('qssp.' + variant) not in program_bins:
raise gf.store.StoreError('unsupported qssp variant: %s' % variant)
qssp = QSSPConfig(qssp_version=variant)
if variant != 'ppeg2017':
qssp.time_region = (
gf.Timing('begin-50'),
gf.Timing('end+100'))
qssp.cut = (
gf.Timing('begin-50'),
gf.Timing('end+100'))
else: # variant == 'ppeg2017':
qssp.frequency_max = 0.5
qssp.time_region = [
gf.Timing('-100'), gf.Timing('{stored:begin}+100')]
qssp.cut = [
gf.Timing('-100'), gf.Timing('{stored:begin}+100')]
qssp.antialiasing_factor = 1.0e-10
qssp.toroidal_modes = False
qssp.cutoff_harmonic_degree_min = 2500
qssp.cutoff_harmonic_degree_max = 2500
qssp.crit_frequency_sge = 5.0
qssp.crit_harmonic_degree_sge = 50000
qssp.source_patch_radius = 10.0
qssp.bandpass_order = 6
qssp.bandpass_corner_low = 0.0
qssp.bandpass_corner_high = 0.125
store_id = os.path.basename(os.path.realpath(store_dir))
if variant == 'ppeg2017':
quantity = 'acceleration'
else:
quantity = None
if variant == 'ppeg2017':
sample_rate = 4.0
else:
sample_rate = 0.2
config = gf.meta.ConfigTypeA(
id=store_id,
ncomponents=10,
component_scheme='elastic10',
stored_quantity=quantity,
sample_rate=sample_rate,
receiver_depth=0*km,
source_depth_min=10*km,
source_depth_max=20*km,
source_depth_delta=10*km,
distance_min=100*km,
distance_max=1000*km,
distance_delta=10*km,
earthmodel_1d=cake.load_model(),
modelling_code_id='qssp',
tabulated_phases=[
gf.meta.TPDef(
id='begin',
definition='p,P,p\\,P\\,Pv_(cmb)p'),
gf.meta.TPDef(
id='end',
definition='2.5'),
gf.meta.TPDef(
id='P',
definition='!P'),
gf.meta.TPDef(
id='S',
definition='!S'),
gf.meta.TPDef(
id='p',
definition='!p'),
gf.meta.TPDef(
id='s',
definition='!s')])
config.validate()
return gf.store.Store.create_editables(
store_dir,
config=config,
extra={'qssp': qssp})
def build(
store_dir,
force=False,
nworkers=None,
continue_=False,
step=None,
iblock=None):
return QSSPGFBuilder.build(
store_dir, force=force, nworkers=nworkers, continue_=continue_,
step=step, iblock=iblock)