Source code for pyrocko.fomosto.psgrn_pscmp

# http://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------

import logging
import warnings
import os
from os.path import join as pjoin
import signal
import shutil
import copy

import math
import numpy as num

from tempfile import mkdtemp
from subprocess import Popen, PIPE

from pyrocko.guts import Float, Int, Tuple, List, Object, String
from pyrocko.model import Location
from pyrocko import gf, util, trace, cake


# how to call the programs
program_bins = {
    'pscmp.2008a': 'fomosto_pscmp2008a',
    'psgrn.2008a': 'fomosto_psgrn2008a'
}

psgrn_displ_names = ('uz', 'ur', 'ut')
psgrn_stress_names = ('szz', 'srr', 'stt', 'szr', 'srt', 'stz')
psgrn_tilt_names = ('tr', 'tt', 'rot')
psgrn_gravity_names = ('gd', 'gr')
psgrn_components = 'ep ss ds cl'.split()

km = 1000.
day = 3600. * 24

guts_prefix = 'pf'
logger = logging.getLogger('pyrocko.fomosto.psgrn_pscmp')


def have_backend():
    for cmd in [[exe] for exe in program_bins.values()]:
        try:
            p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
            (stdout, stderr) = p.communicate()

        except OSError:
            return False

    return True


def nextpow2(i):
    return 2 ** int(math.ceil(math.log(i) / math.log(2.)))


def str_float_vals(vals):
    return ' '.join('%e' % val for val in vals)


def str_int_vals(vals):
    return ' '.join('%i' % val for val in vals)


def str_str_vals(vals):
    return ' '.join("'%s'" % val for val in vals)


def cake_model_to_config(mod):
    srows = []
    for ir, row in enumerate(mod.to_scanlines(get_burgers=True)):
        depth, vp, vs, rho, qp, qs, eta1, eta2, alpha = row
        # replace qs with etas = 0.
        row = [depth / km, vp / km, vs / km, rho, eta1, eta2, alpha]
        srows.append('%i %15s' % (ir + 1, str_float_vals(row)))

    return '\n'.join(srows), len(srows)


[docs]class PsGrnSpatialSampling(Object): ''' Definition of spatial sampling for PSGRN. Note: attributes in this class use non-standard units [km] to be consistent with PSGRN text file input. Please read the documentation carefully. ''' n_steps = Int.T(default=10) start_distance = Float.T(default=0.) # start sampling [km] from source end_distance = Float.T(default=100.) # end def string_for_config(self): return '%i %15e %15e' % (self.n_steps, self.start_distance, self.end_distance)
[docs]class PsGrnConfig(Object): ''' Configuration for PSGRN. Note: attributes in this class use non-standard units [km] and [days] to be consistent with PSGRN text file input. Please read the documentation carefully. ''' version = String.T(default='2008a') sampling_interval = Float.T( default=1.0, help='Exponential sampling 1.- equidistant, > 1. decreasing sampling' ' with distance') n_time_samples = Int.T( optional=True, help='Number of temporal GF samples up to max_time. Has to be equal' ' to a power of 2! If not, next power of 2 is taken.') max_time = Float.T( optional=True, help='Maximum time [days] after seismic event.') gf_depth_spacing = Float.T( optional=True, help='Depth spacing [km] for the observation points. ' 'If not defined depth spacing from the `StoreConfig` is used') gf_distance_spacing = Float.T( optional=True, help='Spatial spacing [km] for the observation points. ' 'If not defined distance spacing from the `StoreConfig` is used') observation_depth = Float.T( default=0., help='Depth of observation points [km]') def items(self): return dict(self.T.inamevals(self))
[docs]class PsGrnConfigFull(PsGrnConfig): earthmodel_1d = gf.meta.Earthmodel1D.T(optional=True) psgrn_outdir = String.T(default='psgrn_green/') distance_grid = PsGrnSpatialSampling.T(PsGrnSpatialSampling.D()) depth_grid = PsGrnSpatialSampling.T(PsGrnSpatialSampling.D()) sw_source_regime = Int.T(default=1) # 1-continental, 0-ocean sw_gravity = Int.T(default=0) accuracy_wavenumber_integration = Float.T(default=0.025) displ_filenames = Tuple.T(3, String.T(), default=psgrn_displ_names) stress_filenames = Tuple.T(6, String.T(), default=psgrn_stress_names) tilt_filenames = Tuple.T(3, String.T(), psgrn_tilt_names) gravity_filenames = Tuple.T(2, String.T(), psgrn_gravity_names) @staticmethod def example(): conf = PsGrnConfigFull() conf.earthmodel_1d = cake.load_model().extract(depth_max=100*km) conf.psgrn_outdir = 'TEST_psgrn_functions/' return conf def string_for_config(self): assert self.earthmodel_1d is not None d = self.__dict__.copy() model_str, nlines = cake_model_to_config(self.earthmodel_1d) d['n_t2'] = nextpow2(self.n_time_samples) d['n_model_lines'] = nlines d['model_lines'] = model_str d['str_psgrn_outdir'] = "'%s'" % './' d['str_displ_filenames'] = str_str_vals(self.displ_filenames) d['str_stress_filenames'] = str_str_vals(self.stress_filenames) d['str_tilt_filenames'] = str_str_vals(self.tilt_filenames) d['str_gravity_filenames'] = str_str_vals(self.gravity_filenames) d['str_distance_grid'] = self.distance_grid.string_for_config() d['str_depth_grid'] = self.depth_grid.string_for_config() template = '''# autogenerated PSGRN input by psgrn.py #============================================================================= # This is input file of FORTRAN77 program "psgrn08a" for computing responses # (Green's functions) of a multi-layered viscoelastic halfspace to point # dislocation sources buried at different depths. All results will be stored in # the given directory and provide the necessary data base for the program # "pscmp07a" for computing time-dependent deformation, geoid and gravity changes # induced by an earthquake with extended fault planes via linear superposition. # For more details, please read the accompanying READ.ME file. # # written by Rongjiang Wang # GeoForschungsZentrum Potsdam # e-mail: wang@gfz-potsdam.de # phone +49 331 2881209 # fax +49 331 2881204 # # Last modified: Potsdam, Jan, 2008 # ################################################################# ## ## ## Cylindrical coordinates (Z positive downwards!) are used. ## ## ## ## If not specified otherwise, SI Unit System is used overall! ## ## ## ################################################################# # #------------------------------------------------------------------------------ # # PARAMETERS FOR SOURCE-OBSERVATION CONFIGURATIONS # ================================================ # 1. the uniform depth of the observation points [km], switch for oceanic (0) # or continental(1) earthquakes; # 2. number of (horizontal) observation distances (> 1 and <= nrmax defined in # psgglob.h), start and end distances [km], ratio (>= 1.0) between max. and # min. sampling interval (1.0 for equidistant sampling); # 3. number of equidistant source depths (>= 1 and <= nzsmax defined in # psgglob.h), start and end source depths [km]; # # r1,r2 = minimum and maximum horizontal source-observation # distances (r2 > r1). # zs1,zs2 = minimum and maximum source depths (zs2 >= zs1 > 0). # # Note that the same sampling rates dr_min and dzs will be used later by the # program "pscmp07a" for discretizing the finite source planes to a 2D grid # of point sources. #------------------------------------------------------------------------------ %(observation_depth)e %(sw_source_regime)i %(str_distance_grid)s %(sampling_interval)e %(str_depth_grid)s #------------------------------------------------------------------------------ # # PARAMETERS FOR TIME SAMPLING # ============================ # 1. number of time samples (<= ntmax def. in psgglob.h) and time window [days]. # # Note that nt (> 0) should be power of 2 (the fft-rule). If nt = 1, the # coseismic (t = 0) changes will be computed; If nt = 2, the coseismic # (t = 0) and steady-state (t -> infinity) changes will be computed; # Otherwise, time series for the given time samples will be computed. # #------------------------------------------------------------------------------ %(n_t2)i %(max_time)f #------------------------------------------------------------------------------ # # PARAMETERS FOR WAVENUMBER INTEGRATION # ===================================== # 1. relative accuracy of the wave-number integration (suggested: 0.1 - 0.01) # 2. factor (> 0 and < 1) for including influence of earth's gravity on the # deformation field (e.g. 0/1 = without / with 100percent gravity effect). #------------------------------------------------------------------------------ %(accuracy_wavenumber_integration)e %(sw_gravity)i #------------------------------------------------------------------------------ # # PARAMETERS FOR OUTPUT FILES # =========================== # # 1. output directory # 2. file names for 3 displacement components (uz, ur, ut) # 3. file names for 6 stress components (szz, srr, stt, szr, srt, stz) # 4. file names for radial and tangential tilt components (as measured by a # borehole tiltmeter), rigid rotation of horizontal plane, geoid and gravity # changes (tr, tt, rot, gd, gr) # # Note that all file or directory names should not be longer than 80 # characters. Directory and subdirectoy names must be separated and ended # by / (unix) or \ (dos)! All file names should be given without extensions # that will be appended automatically by ".ep" for the explosion (inflation) # source, ".ss" for the strike-slip source, ".ds" for the dip-slip source, # and ".cl" for the compensated linear vector dipole source) # #------------------------------------------------------------------------------ %(str_psgrn_outdir)s %(str_displ_filenames)s %(str_stress_filenames)s %(str_tilt_filenames)s %(str_gravity_filenames)s #------------------------------------------------------------------------------ # # GLOBAL MODEL PARAMETERS # ======================= # 1. number of data lines of the layered model (<= lmax as defined in psgglob.h) # # The surface and the upper boundary of the half-space as well as the # interfaces at which the viscoelastic parameters are continuous, are all # defined by a single data line; All other interfaces, at which the # viscoelastic parameters are discontinuous, are all defined by two data # lines (upper-side and lower-side values). This input format could also be # used for a graphic plot of the layered model. Layers which have different # parameter values at top and bottom, will be treated as layers with a # constant gradient, and will be discretised to a number of homogeneous # sublayers. Errors due to the discretisation are limited within about # 5percent (changeable, see psgglob.h). # # 2.... parameters of the multilayered model # # Burgers rheology (a Kelvin-Voigt body and a Maxwell body in series # connection) for relaxation of shear modulus is implemented. No relaxation # of compressional modulus is considered. # # eta1 = transient viscosity (dashpot of the Kelvin-Voigt body; <= 0 means # infinity value) # eta2 = steady-state viscosity (dashpot of the Maxwell body; <= 0 means # infinity value) # alpha = ratio between the effective and the unrelaxed shear modulus # = mu1/(mu1+mu2) (> 0 and <= 1) # # Special cases: # (1) Elastic: eta1 and eta2 <= 0 (i.e. infinity); alpha meaningless # (2) Maxwell body: eta1 <= 0 (i.e. eta1 = infinity) # or alpha = 1 (i.e. mu1 = infinity) # (3) Standard-Linear-Solid: eta2 <= 0 (i.e. infinity) #------------------------------------------------------------------------------ %(n_model_lines)i |int: no_model_lines; #------------------------------------------------------------------------------ # no depth[km] vp[km/s] vs[km/s] rho[kg/m^3] eta1[Pa*s] eta2[Pa*s] alpha #------------------------------------------------------------------------------ %(model_lines)s #=======================end of input=========================================== ''' # noqa return (template % d).encode('ascii')
class PsGrnError(gf.store.StoreError): pass def remove_if_exists(fn, force=False): if os.path.exists(fn): if force: os.remove(fn) else: raise gf.CannotCreate('file %s already exists' % fn)
[docs]class PsGrnRunner(object): ''' Wrapper object to execute the program fomosto_psgrn. ''' def __init__(self, outdir): outdir = os.path.abspath(outdir) if not os.path.exists(outdir): os.mkdir(outdir) self.outdir = outdir self.config = None
[docs] def run(self, config, force=False): ''' Run the program with the specified configuration. :param config: :py:class:`PsGrnConfigFull` :param force: boolean, set true to overwrite existing output ''' self.config = config input_fn = pjoin(self.outdir, 'input') remove_if_exists(input_fn, force=force) with open(input_fn, 'wb') as f: input_str = config.string_for_config() logger.debug('===== begin psgrn input =====\n' '%s===== end psgrn input =====' % input_str.decode()) f.write(input_str) program = program_bins['psgrn.%s' % config.version] old_wd = os.getcwd() os.chdir(self.outdir) 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 PsGrnError( '''could not start psgrn 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 psgrn output =====\n' '%s===== end psgrn output =====' % output_str.decode()) errmess = [] if proc.returncode != 0: errmess.append( 'psgrn had a non-zero exit state: %i' % proc.returncode) if error_str: logger.warning( 'psgrn emitted something via stderr: \n\n%s' % error_str.decode()) # errmess.append('psgrn emitted something via stderr') if output_str.lower().find(b'error') != -1: errmess.append("the string 'error' appeared in psgrn output") if errmess: os.chdir(old_wd) raise PsGrnError(''' ===== begin psgrn input ===== %s===== end psgrn input ===== ===== begin psgrn output ===== %s===== end psgrn output ===== ===== begin psgrn error ===== %s===== end psgrn error ===== %s psgrn has been invoked as "%s" in the directory %s'''.lstrip() % ( input_str.decode(), output_str.decode(), error_str.decode(), '\n'.join(errmess), program, self.outdir)) self.psgrn_output = output_str self.psgrn_error = error_str os.chdir(old_wd)
pscmp_displ_names = ('un', 'ue', 'ud') pscmp_stress_names = ('snn', 'see', 'sdd', 'sne', 'snd', 'sed') pscmp_tilt_names = ('tn', 'te', 'rot') pscmp_gravity_names = ('gd', 'gr') pscmp_all_names = pscmp_displ_names + pscmp_stress_names + pscmp_tilt_names + \ pscmp_gravity_names pscmp_component_mapping = { 'displ': (pscmp_displ_names, (2, 5)), 'stress': (pscmp_stress_names, (5, 11)), 'tilt': (pscmp_tilt_names, (11, 14)), 'gravity': (pscmp_gravity_names, (14, 16)), 'all': (pscmp_all_names, (2, 16)), } def dsin(value): return num.sin(value * num.pi / 180.) def dcos(value): return num.cos(value * num.pi / 180.)
[docs]def distributed_fault_patches_to_config(patches): ''' Input: List of PsCmpRectangularSource(s) ''' srows = [] for i, patch in enumerate(patches): srows.append('%i %s' % (i + 1, patch.string_for_config())) return '\n'.join(srows), len(srows)
[docs]class PsCmpObservation(Object): pass
[docs]class PsCmpScatter(PsCmpObservation): ''' Scattered observation points. ''' lats = List.T(Float.T(), optional=True, default=[10.4, 10.5]) lons = List.T(Float.T(), optional=True, default=[12.3, 13.4]) def string_for_config(self): srows = [] for lat, lon in zip(self.lats, self.lons): srows.append('(%15f, %15f)' % (lat, lon)) self.sw = 0 return ' %i' % (len(srows)), '\n'.join(srows)
[docs]class PsCmpProfile(PsCmpObservation): ''' Calculation along observation profile. ''' n_steps = Int.T(default=10) slat = Float.T( default=0., help='Profile start latitude') slon = Float.T( default=0., help='Profile start longitude') elat = Float.T( default=0., help='Profile end latitude') elon = Float.T( default=0., help='Profile end longitude') distances = List.T( Float.T(), optional=True, help='Distances [m] for each point on profile from start to end.') def string_for_config(self): self.sw = 1 return ' %i' % (self.n_steps), \ ' ( %15f, %15f ), ( %15f, %15f )' % ( self.slat, self.slon, self.elat, self.elon)
[docs]class PsCmpArray(PsCmpObservation): ''' Calculation on a grid. ''' n_steps_lat = Int.T(default=10) n_steps_lon = Int.T(default=10) slat = Float.T( default=0., help='Array start latitude') slon = Float.T( default=0., help='Array start longitude') elat = Float.T( default=0., help='Array end latitude') elon = Float.T( default=0., help='Array end longitude') def string_for_config(self): self.sw = 2 return ' %i %15f %15f ' % ( self.n_steps_lat, self.slat, self.elat), \ ' %i %15f %15f ' % ( self.n_steps_lon, self.slon, self.elon)
[docs]class PsCmpRectangularSource(Location, gf.seismosizer.Cloneable): ''' Rectangular source for the input geometry of the active fault. The default shift of the origin (:gattr:`pos_s`, :gattr:`pos_d`) with respect to the reference coordinates (:gattr:`~pyrocko.model.location.Location.lat`, :gattr:`~pyrocko.model.location.Location.lon`) is zero, which implies that the reference is the center of the fault plane! The calculation point is always the center of the fault-plane. Setting :gattr:`pos_s` or :gattr:`pos_d` moves the fault point with respect to the origin along strike and dip direction, respectively. ''' length = Float.T( default=6.0 * km, help='Fault length [m].') width = Float.T( default=5.0 * km, help='Fault width [m].') strike = Float.T( default=0.0, help='Fault strike, clockwise from north [deg].') dip = Float.T( default=90.0, help='Fault dip, downward from horizontal [deg].') rake = Float.T( default=0.0, help='Rake angle, counter-clock wise from right-horizontal [deg].') torigin = Float.T( default=0.0, help='Origin time [s].') slip = Float.T( optional=True, default=1.0, help='Slip amount [m]') pos_s = Float.T( optional=True, default=None, help='Origin offset along strike [m].') pos_d = Float.T( optional=True, default=None, help='Origin offset along dip [m].') opening = Float.T( default=0.0, help='Fault opening [m].')
[docs] def update(self, **kwargs): ''' Change some of the source models parameters. Example:: >>> from pyrocko import gf >>> s = gf.DCSource() >>> s.update(strike=66., dip=33.) >>> print(s) --- !pf.DCSource depth: 0.0 time: 1970-01-01 00:00:00 magnitude: 6.0 strike: 66.0 dip: 33.0 rake: 0.0 ''' for (k, v) in kwargs.items(): self[k] = v
@property def dip_slip(self): return float(self.slip * dsin(self.rake) * (-1)) @property def strike_slip(self): return float(self.slip * dcos(self.rake)) def string_for_config(self): if self.pos_s or self.pos_d is None: self.pos_s = 0. self.pos_d = 0. tempd = copy.deepcopy(self.__dict__) tempd['dip_slip'] = self.dip_slip tempd['strike_slip'] = self.strike_slip tempd['effective_lat'] = self.effective_lat tempd['effective_lon'] = self.effective_lon tempd['depth'] /= km tempd['length'] /= km tempd['width'] /= km return '%(effective_lat)15f %(effective_lon)15f %(depth)15f' \ '%(length)15f %(width)15f %(strike)15f' \ '%(dip)15f 1 1 %(torigin)15f \n %(pos_s)15f %(pos_d)15f ' \ '%(strike_slip)15f %(dip_slip)15f %(opening)15f' % tempd
MTIso = { 'nn': dict(strike=90., dip=90., rake=0., slip=0., opening=1.), 'ee': dict(strike=0., dip=90., rake=0., slip=0., opening=1.), 'dd': dict(strike=0., dip=0., rake=-90., slip=0., opening=1.), } MTDev = { 'ne': dict(strike=90., dip=90., rake=180., slip=1., opening=0.), 'nd': dict(strike=180., dip=0., rake=0., slip=1., opening=0.), 'ed': dict(strike=270., dip=0., rake=0., slip=1., opening=0.), }
[docs]def get_nullification_factor(mu, lame_lambda): ''' Factor that needs to be multiplied to 2 of the tensile sources to eliminate two of the isotropic components. ''' return -lame_lambda / (2. * mu + 2. * lame_lambda)
[docs]def get_trace_normalization_factor(mu, lame_lambda, nullification): ''' Factor to be multiplied to elementary GF trace to have unit displacement. ''' return 1. / ((2. * mu) + lame_lambda + (2. * lame_lambda * nullification))
def get_iso_scaling_factors(mu, lame_lambda): nullf = get_nullification_factor(mu, lame_lambda) scale = get_trace_normalization_factor(mu, lame_lambda, nullf) return nullf, scale
[docs]class PsCmpTensileSF(Location, gf.seismosizer.Cloneable): ''' Compound dislocation of 3 perpendicular, rectangular sources to approximate an opening single force couple. NED coordinate system! Warning: Mixed standard [m] / non-standard [days] units are used in this class. Please read the documentation carefully. ''' length = Float.T( default=1.0 * km, help='Length of source rectangle [m].') width = Float.T( default=1.0 * km, help='Width of source rectangle [m].') torigin = Float.T( default=0.0, help='Origin time [days]') idx = String.T( default='nn', help='Axis index for opening direction; "nn", "ee", or "dd"') def to_rfs(self, nullification): cmpd = [] for comp, mt in MTIso.items(): params = copy.deepcopy(mt) if comp != self.idx: params = copy.deepcopy(mt) params['opening'] *= nullification kwargs = copy.deepcopy(self.__dict__) kwargs.update(params) kwargs.pop('idx') kwargs.pop('_latlon') cmpd.append(PsCmpRectangularSource(**kwargs)) return cmpd
[docs]class PsCmpShearSF(Location, gf.seismosizer.Cloneable): ''' Shear fault source model component. Warning: Mixed standard [m] / non-standard [days] units are used in this class. Please read the documentation carefully. ''' length = Float.T( default=1.0 * km, help='Length of source rectangle [m].') width = Float.T( default=1.0 * km, help='Width of source rectangle [m].') torigin = Float.T( default=0.0, help='Origin time [days]') idx = String.T( default='ne', help='Axis index for opening direction; "ne", "nd", or "ed"') def to_rfs(self): kwargs = copy.deepcopy(self.__dict__) kwargs.pop('idx') kwargs.pop('_latlon') kwargs.update(MTDev[self.idx]) return [PsCmpRectangularSource(**kwargs)]
[docs]class PsCmpMomentTensor(Location, gf.seismosizer.Cloneable): ''' Mapping of Moment Tensor components to rectangular faults. Only one component at a time valid! NED coordinate system! Warning: Mixed standard [m] / non-standard [days] units are used in this class. Please read the documentation carefully. ''' length = Float.T( default=1.0 * km, help='Length of source rectangle [m].') width = Float.T( default=1.0 * km, help='Width of source rectangle [m].') torigin = Float.T( default=0.0, help='Origin time [days]') idx = String.T( default='nn', help='Axis index for MT component; ' '"nn", "ee", "dd", "ne", "nd", or "ed".') def to_rfs(self, nullification=-0.25): kwargs = copy.deepcopy(self.__dict__) kwargs.pop('_latlon') if self.idx in MTIso: tsf = PsCmpTensileSF(**kwargs) return tsf.to_rfs(nullification) elif self.idx in MTDev: ssf = PsCmpShearSF(**kwargs) return ssf.to_rfs() else: raise Exception('MT component not supported!')
[docs]class PsCmpCoulombStress(Object): pass
[docs]class PsCmpCoulombStressMasterFault(PsCmpCoulombStress): friction = Float.T(default=0.7) skempton_ratio = Float.T(default=0.0) master_fault_strike = Float.T(default=300.) master_fault_dip = Float.T(default=15.) master_fault_rake = Float.T(default=90.) sigma1 = Float.T(default=1.e6, help='[Pa]') sigma2 = Float.T(default=-1.e6, help='[Pa]') sigma3 = Float.T(default=0.0, help='[Pa]') def string_for_config(self): return '%(friction)15e %(skempton_ratio)15e %(master_fault_strike)15f'\ '%(master_fault_dip)15f %(master_fault_rake)15f'\ '%(sigma1)15e %(sigma2)15e %(sigma3)15e' % self.__dict__
[docs]class PsCmpSnapshots(Object): ''' Snapshot time series definition. Note: attributes in this class use non-standard units [days] to be consistent with PSCMP text file input. Please read the documentation carefully. ''' tmin = Float.T( default=0.0, help='Time [days] after source time to start temporal sample' ' snapshots.') tmax = Float.T( default=1.0, help='Time [days] after source time to end temporal sample f.') deltatdays = Float.T( default=1.0, help='Sample period [days].') @property def times(self): nt = int(num.ceil((self.tmax - self.tmin) / self.deltatdays)) return num.linspace(self.tmin, self.tmax, nt).tolist() @property def deltat(self): return self.deltatdays * 24 * 3600
[docs]class PsCmpConfig(Object): version = String.T(default='2008a') # scatter, profile or array observation = PsCmpObservation.T(default=PsCmpScatter.D()) rectangular_fault_size_factor = Float.T( default=1., help='The size of the rectangular faults in the compound MT' ' :py:class:`PsCmpMomentTensor` is dependend on the horizontal' ' spacing of the GF database. This factor is applied to the' ' relationship in i.e. fault length & width = f * dx.') snapshots = PsCmpSnapshots.T( optional=True) rectangular_source_patches = List.T(PsCmpRectangularSource.T()) def items(self): return dict(self.T.inamevals(self))
[docs]class PsCmpConfigFull(PsCmpConfig): pscmp_outdir = String.T(default='./') psgrn_outdir = String.T(default='./psgrn_functions/') los_vector = Tuple.T(3, Float.T(), optional=True) sw_los_displacement = Int.T(default=0) sw_coulomb_stress = Int.T(default=0) coulomb_master_field = PsCmpCoulombStress.T( optional=True, default=PsCmpCoulombStressMasterFault.D()) displ_sw_output_types = Tuple.T(3, Int.T(), default=(0, 0, 0)) stress_sw_output_types = Tuple.T(6, Int.T(), default=(0, 0, 0, 0, 0, 0)) tilt_sw_output_types = Tuple.T(3, Int.T(), default=(0, 0, 0)) gravity_sw_output_types = Tuple.T(2, Int.T(), default=(0, 0)) indispl_filenames = Tuple.T(3, String.T(), default=psgrn_displ_names) instress_filenames = Tuple.T(6, String.T(), default=psgrn_stress_names) intilt_filenames = Tuple.T(3, String.T(), default=psgrn_tilt_names) ingravity_filenames = Tuple.T(2, String.T(), default=psgrn_gravity_names) outdispl_filenames = Tuple.T(3, String.T(), default=pscmp_displ_names) outstress_filenames = Tuple.T(6, String.T(), default=pscmp_stress_names) outtilt_filenames = Tuple.T(3, String.T(), default=pscmp_tilt_names) outgravity_filenames = Tuple.T(2, String.T(), default=pscmp_gravity_names) snapshot_basefilename = String.T(default='snapshot') @classmethod def example(cls): conf = cls() conf.psgrn_outdir = 'TEST_psgrn_functions/' conf.pscmp_outdir = 'TEST_pscmp_output/' conf.rectangular_source_patches = [PsCmpRectangularSource( lat=10., lon=10., slip=2., width=5., length=10., strike=45, dip=30, rake=-90)] conf.observation = PsCmpArray( slat=9.5, elat=10.5, n_steps_lat=150, slon=9.5, elon=10.5, n_steps_lon=150) return conf def get_output_filenames(self, rundir): return [pjoin(rundir, self.snapshot_basefilename + '_' + str(i + 1) + '.txt') for i in range(len(self.snapshots.times))] def string_for_config(self): d = self.__dict__.copy() patches_str, n_patches = distributed_fault_patches_to_config( self.rectangular_source_patches) d['patches_str'] = patches_str d['n_patches'] = n_patches str_npoints, str_observation = self.observation.string_for_config() d['str_npoints'] = str_npoints d['str_observation'] = str_observation d['sw_observation_type'] = self.observation.sw if self.snapshots.times: str_times_dummy = [] for i, time in enumerate(self.snapshots.times): str_times_dummy.append("%f '%s_%i.txt'\n" % ( time, self.snapshot_basefilename, i + 1)) str_times_dummy.append('#') d['str_times_snapshots'] = ''.join(str_times_dummy) d['n_snapshots'] = len(str_times_dummy) - 1 else: d['str_times_snapshots'] = '# ' d['n_snapshots'] = 0 if self.sw_los_displacement == 1: d['str_los_vector'] = str_float_vals(self.los_vector) else: d['str_los_vector'] = '' if self.sw_coulomb_stress == 1: d['str_coulomb_master_field'] = \ self.coulomb_master_field.string_for_config() else: d['str_coulomb_master_field'] = '' d['str_psgrn_outdir'] = "'%s'" % self.psgrn_outdir d['str_pscmp_outdir'] = "'%s'" % './' d['str_indispl_filenames'] = str_str_vals(self.indispl_filenames) d['str_instress_filenames'] = str_str_vals(self.instress_filenames) d['str_intilt_filenames'] = str_str_vals(self.intilt_filenames) d['str_ingravity_filenames'] = str_str_vals(self.ingravity_filenames) d['str_outdispl_filenames'] = str_str_vals(self.outdispl_filenames) d['str_outstress_filenames'] = str_str_vals(self.outstress_filenames) d['str_outtilt_filenames'] = str_str_vals(self.outtilt_filenames) d['str_outgravity_filenames'] = str_str_vals(self.outgravity_filenames) d['str_displ_sw_output_types'] = str_int_vals( self.displ_sw_output_types) d['str_stress_sw_output_types'] = str_int_vals( self.stress_sw_output_types) d['str_tilt_sw_output_types'] = str_int_vals( self.tilt_sw_output_types) d['str_gravity_sw_output_types'] = str_int_vals( self.gravity_sw_output_types) template = '''# autogenerated PSCMP input by pscmp.py #=============================================================================== # This is input file of FORTRAN77 program "pscmp08" for modeling post-seismic # deformation induced by earthquakes in multi-layered viscoelastic media using # the Green's function approach. The earthquke source is represented by an # arbitrary number of rectangular dislocation planes. For more details, please # read the accompanying READ.ME file. # # written by Rongjiang Wang # GeoForschungsZentrum Potsdam # e-mail: wang@gfz-potsdam.de # phone +49 331 2881209 # fax +49 331 2881204 # # Last modified: Potsdam, July, 2008 # # References: # # (1) Wang, R., F. Lorenzo-Martin and F. Roth (2003), Computation of deformation # induced by earthquakes in a multi-layered elastic crust - FORTRAN programs # EDGRN/EDCMP, Computer and Geosciences, 29(2), 195-207. # (2) Wang, R., F. Lorenzo-Martin and F. Roth (2006), PSGRN/PSCMP - a new code for # calculating co- and post-seismic deformation, geoid and gravity changes # based on the viscoelastic-gravitational dislocation theory, Computers and # Geosciences, 32, 527-541. DOI:10.1016/j.cageo.2005.08.006. # (3) Wang, R. (2005), The dislocation theory: a consistent way for including the # gravity effect in (visco)elastic plane-earth models, Geophysical Journal # International, 161, 191-196. # ################################################################# ## ## ## Green's functions should have been prepared with the ## ## program "psgrn08" before the program "pscmp08" is started. ## ## ## ## For local Cartesian coordinate system, the Aki's convention ## ## is used, that is, x is northward, y is eastward, and z is ## ## downward. ## ## ## ## If not specified otherwise, SI Unit System is used overall! ## ## ## ################################################################# #=============================================================================== # OBSERVATION ARRAY # ================= # 1. selection for irregular observation positions (= 0) or a 1D observation # profile (= 1) or a rectangular 2D observation array (= 2): iposrec # # IF (iposrec = 0 for irregular observation positions) THEN # # 2. number of positions: nrec # # 3. coordinates of the observations: (lat(i),lon(i)), i=1,nrec # # ELSE IF (iposrec = 1 for regular 1D observation array) THEN # # 2. number of position samples of the profile: nrec # # 3. the start and end positions: (lat1,lon1), (lat2,lon2) # # ELSE IF (iposrec = 2 for rectanglular 2D observation array) THEN # # 2. number of x samples, start and end values: nxrec, xrec1, xrec2 # # 3. number of y samples, start and end values: nyrec, yrec1, yrec2 # # sequence of the positions in output data: lat(1),lon(1); ...; lat(nx),lon(1); # lat(1),lon(2); ...; lat(nx),lon(2); ...; lat(1),lon(ny); ...; lat(nx),lon(ny). # # Note that the total number of observation positions (nrec or nxrec*nyrec) # should be <= NRECMAX (see pecglob.h)! #=============================================================================== %(sw_observation_type)i %(str_npoints)s %(str_observation)s #=============================================================================== # OUTPUTS # ======= # # 1. select output for los displacement (only for snapshots, see below), x, y, # and z-cosines to the INSAR orbit: insar (1/0 = yes/no), xlos, ylos, zlos # # if this option is selected (insar = 1), the snapshots will include additional # data: # LOS_Dsp = los displacement to the given satellite orbit. # # 2. select output for Coulomb stress changes (only for snapshots, see below): # icmb (1/0 = yes/no), friction, Skempton ratio, strike, dip, and rake angles # [deg] describing the uniform regional master fault mechanism, the uniform # regional principal stresses: sigma1, sigma2 and sigma3 [Pa] in arbitrary # order (the orietation of the pre-stress field will be derived by assuming # that the master fault is optimally oriented according to Coulomb failure # criterion) # # if this option is selected (icmb = 1), the snapshots will include additional # data: # CMB_Fix, Sig_Fix = Coulomb and normal stress changes on master fault; # CMB_Op1/2, Sig_Op1/2 = Coulomb and normal stress changes on the two optimally # oriented faults; # Str_Op1/2, Dip_Op1/2, Slp_Op1/2 = strike, dip and rake angles of the two # optimally oriented faults. # # Note: the 1. optimally orieted fault is the one closest to the master fault. # # 3. output directory in char format: outdir # # 4. select outputs for displacement components (1/0 = yes/no): itout(i), i=1-3 # # 5. the file names in char format for the x, y, and z components: # toutfile(i), i=1-3 # # 6. select outputs for stress components (1/0 = yes/no): itout(i), i=4-9 # # 7. the file names in char format for the xx, yy, zz, xy, yz, and zx components: # toutfile(i), i=4-9 # # 8. select outputs for vertical NS and EW tilt components, block rotation, geoid # and gravity changes (1/0 = yes/no): itout(i), i=10-14 # # 9. the file names in char format for the NS tilt (positive if borehole top # tilts to north), EW tilt (positive if borehole top tilts to east), block # rotation (clockwise positive), geoid and gravity changes: toutfile(i), i=10-14 # # Note that all above outputs are time series with the time window as same # as used for the Green's functions # #10. number of scenario outputs ("snapshots": spatial distribution of all above # observables at given time points; <= NSCENMAX (see pscglob.h): nsc # #11. the time [day], and file name (in char format) for the 1. snapshot; #12. the time [day], and file name (in char format) for the 2. snapshot; #13. ... # # Note that all file or directory names should not be longer than 80 # characters. Directories must be ended by / (unix) or \ (dos)! #=============================================================================== %(sw_los_displacement)i %(str_los_vector)s %(sw_coulomb_stress)i %(str_coulomb_master_field)s %(str_pscmp_outdir)s %(str_displ_sw_output_types)s %(str_outdispl_filenames)s %(str_stress_sw_output_types)s %(str_outstress_filenames)s %(str_tilt_sw_output_types)s %(str_gravity_sw_output_types)s %(str_outtilt_filenames)s %(str_outgravity_filenames)s %(n_snapshots)i %(str_times_snapshots)s #=============================================================================== # # GREEN'S FUNCTION DATABASE # ========================= # 1. directory where the Green's functions are stored: grndir # # 2. file names (without extensions!) for the 13 Green's functions: # 3 displacement komponents (uz, ur, ut): green(i), i=1-3 # 6 stress components (szz, srr, stt, szr, srt, stz): green(i), i=4-9 # radial and tangential components measured by a borehole tiltmeter, # rigid rotation around z-axis, geoid and gravity changes (tr, tt, rot, gd, gr): # green(i), i=10-14 # # Note that all file or directory names should not be longer than 80 # characters. Directories must be ended by / (unix) or \ (dos)! The # extensions of the file names will be automatically considered. They # are ".ep", ".ss", ".ds" and ".cl" denoting the explosion (inflation) # strike-slip, the dip-slip and the compensated linear vector dipole # sources, respectively. # #=============================================================================== %(str_psgrn_outdir)s %(str_indispl_filenames)s %(str_instress_filenames)s %(str_intilt_filenames)s %(str_ingravity_filenames)s #=============================================================================== # RECTANGULAR SUBFAULTS # ===================== # 1. number of subfaults (<= NSMAX in pscglob.h): ns # # 2. parameters for the 1. rectangular subfault: geographic coordinates # (O_lat, O_lon) [deg] and O_depth [km] of the local reference point on # the present fault plane, length (along strike) [km] and width (along down # dip) [km], strike [deg], dip [deg], number of equi-size fault patches along # the strike (np_st) and along the dip (np_di) (total number of fault patches # = np_st x np_di), and the start time of the rupture; the following data # lines describe the slip distribution on the present sub-fault: # # pos_s[km] pos_d[km] slip_strike[m] slip_downdip[m] opening[m] # # where (pos_s,pos_d) defines the position of the center of each patch in # the local coordinate system with the origin at the reference point: # pos_s = distance along the length (positive in the strike direction) # pos_d = distance along the width (positive in the down-dip direction) # # # 3. ... for the 2. subfault ... # ... # N # / # /| strike # +------------------------ # |\ p . \ W # :-\ i . \ i # | \ l . \ d # :90 \ S . \ t # |-dip\ . \ h # : \. | rake \ # Z ------------------------- # L e n g t h # # Simulation of a Mogi source: # (1) Calculate deformation caused by three small openning plates (each # causes a third part of the volume of the point inflation) located # at the same depth as the Mogi source but oriented orthogonal to # each other. # (2) Multiply the results by 3(1-nu)/(1+nu), where nu is the Poisson # ratio at the source depth. # The multiplication factor is the ratio of the seismic moment (energy) of # the Mogi source to that of the plate openning with the same volume change. #=============================================================================== # n_faults #------------------------------------------------------------------------------- %(n_patches)i #------------------------------------------------------------------------------- # n O_lat O_lon O_depth length width strike dip np_st np_di start_time # [-] [deg] [deg] [km] [km] [km] [deg] [deg] [-] [-] [day] # pos_s pos_d slp_stk slp_ddip open # [km] [km] [m] [m] [m] #------------------------------------------------------------------------------- %(patches_str)s #================================end of input=================================== ''' # noqa return (template % d).encode('ascii')
[docs]class PsGrnPsCmpConfig(Object): ''' Combined config Object of PsGrn and PsCmp. Note: attributes in this class use non-standard units [days] to be consistent with PSCMP text file input. Please read the documentation carefully. ''' tmin_days = Float.T( default=0., help='Min. time in days') tmax_days = Float.T( default=1., help='Max. time in days') gf_outdir = String.T(default='psgrn_functions') psgrn_config = PsGrnConfig.T(default=PsGrnConfig.D()) pscmp_config = PsCmpConfig.T(default=PsCmpConfig.D())
class PsCmpError(gf.store.StoreError): pass class Interrupted(gf.store.StoreError): def __str__(self): return 'Interrupted.'
[docs]class PsCmpRunner(object): ''' Wrapper object to execute the program fomosto_pscmp with the specified configuration. :param tmp: string, path to the temporary directy where calculation results are stored :param keep_tmp: boolean, if True the result directory is kept ''' def __init__(self, tmp=None, keep_tmp=False): if tmp is not None: tmp = os.path.abspath(tmp) self.tempdir = mkdtemp(prefix='pscmprun-', dir=tmp) self.keep_tmp = keep_tmp self.config = None
[docs] def run(self, config): ''' Run the program! :param config: :py:class:`PsCmpConfigFull` ''' 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 pscmp input =====\n' '%s===== end pscmp input =====' % input_str.decode()) f.write(input_str) program = program_bins['pscmp.%s' % config.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, close_fds=True) except OSError as err: os.chdir(old_wd) logger.error('OS error: {0}'.format(err)) raise PsCmpError( '''could not start pscmp 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 pscmp output =====\n' '%s===== end pscmp output =====' % output_str.decode()) errmsg = [] if proc.returncode != 0: errmsg.append( 'pscmp had a non-zero exit state: %i' % proc.returncode) if error_str: errmsg.append('pscmp emitted something via stderr') if output_str.lower().find(b'error') != -1: errmsg.append("the string 'error' appeared in pscmp output") if errmsg: self.keep_tmp = True os.chdir(old_wd) raise PsCmpError(''' ===== begin pscmp input ===== {pscmp_input}===== end pscmp input ===== ===== begin pscmp output ===== {pscmp_output}===== end pscmp output ===== ===== begin pscmp error ===== {pscmp_error}===== end pscmp error ===== {error_messages} pscmp has been invoked as "{call}" in the directory {dir}'''.format( pscmp_input=input_str, pscmp_output=output_str, pscmp_error=error_str, error_messages='\n'.join(errmsg), call=program, dir=self.tempdir) .lstrip()) self.pscmp_output = output_str self.pscmp_error = error_str os.chdir(old_wd)
[docs] def get_results(self, component='displ'): ''' Get the resulting components from the stored directory. .. note:: The z-component is downward positive. :param component: The component to retrieve from the result directory. Choices: ``"displ"`` -- displacement, n x 3 array, ``"stress"`` -- stresses n x 6 array, ``"tilt'`` -- tilts n x 3 array, ``"gravity'`` -- gravity n x 2 array, ``"all"`` -- all the above together. :type param: str ''' assert self.config.snapshots is not None fns = self.config.get_output_filenames(self.tempdir) output = [] for fn in fns: if not os.path.exists(fn): continue data = num.loadtxt(fn, skiprows=1, dtype=float) try: _, idxs = pscmp_component_mapping[component] except KeyError: raise Exception('component %s not supported! Either: %s' % ( component, ', '.join( '"%s"' % k for k in pscmp_component_mapping.keys()))) istart, iend = idxs output.append(data[:, istart:iend]) return output
[docs] def get_traces(self, component='displ'): ''' Load snapshot data array and return specified components. Transform array component and receiver wise to list of :py:class:`pyrocko.trace.Trace` ''' distances = self.config.observation.distances output_list = self.get_results(component=component) deltat = self.config.snapshots.deltat nrec, ncomp = output_list[0].shape outarray = num.dstack([num.zeros([nrec, ncomp])] + output_list) comps, _ = pscmp_component_mapping[component] outtraces = [] for row in range(nrec): for col, comp in enumerate(comps): tr = trace.Trace( '', '%04i' % row, '', comp, tmin=0.0, deltat=deltat, ydata=outarray[row, col, :], meta=dict(distance=distances[row])) outtraces.append(tr) return outtraces
def __del__(self): if self.tempdir: if not self.keep_tmp: shutil.rmtree(self.tempdir) self.tempdir = None else: logger.warning( 'not removing temporary directory: %s' % self.tempdir)
class PsGrnCmpGFBuilder(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') storeconf = self.store.config dummy_lat = 10.0 dummy_lon = 10.0 depths = storeconf.coords[0] lats = num.ones_like(depths) * dummy_lat lons = num.ones_like(depths) * dummy_lon points = num.vstack([lats, lons, depths]).T self.shear_moduli = storeconf.get_shear_moduli( lat=dummy_lat, lon=dummy_lon, points=points, interpolation='multilinear') self.lambda_moduli = storeconf.get_lambda_moduli( lat=dummy_lat, lon=dummy_lon, points=points, interpolation='multilinear') if step == 0: block_size = (1, storeconf.nsource_depths, storeconf.ndistances) else: if block_size is None: block_size = (1, 1, storeconf.ndistances) if len(storeconf.ns) == 2: block_size = block_size[1:] gf.builder.Builder.__init__( self, storeconf, step, block_size=block_size, force=force) baseconf = self.store.get_extra('psgrn_pscmp') cg = PsGrnConfigFull(**baseconf.psgrn_config.items()) if cg.n_time_samples is None or cg.max_time is None: deltat_days = 1. / storeconf.sample_rate / day cg.n_time_samples = int(baseconf.tmax_days // deltat_days) cg.max_time = baseconf.tmax_days else: warnings.warn( 'PsGrnConfig is defining n_times_samples and max_time,' ' this is replaced by PsGrnPsCmpConfig tmin and tmax.', FutureWarning) cc = PsCmpConfigFull(**baseconf.pscmp_config.items()) if cc.snapshots is None: deltat_days = 1. / storeconf.sample_rate / day cc.snapshots = PsCmpSnapshots( tmin=baseconf.tmin_days, tmax=baseconf.tmax_days, deltatdays=deltat_days) else: warnings.warn( 'PsCmpConfig is defining snapshots,' ' this is replaced by PsGrnPsCmpConfig tmin and tmax.', FutureWarning) cg.earthmodel_1d = storeconf.earthmodel_1d gf_outpath = os.path.join(store_dir, baseconf.gf_outdir) cg.psgrn_outdir = gf_outpath + '/' cc.psgrn_outdir = gf_outpath + '/' util.ensuredir(gf_outpath) self.cg = cg self.cc = cc def cleanup(self): self.store.close() def work_block(self, iblock): if len(self.store.config.ns) == 2: (sz, firstx), (sz, lastx), (ns, nx) = \ self.get_block_extents(iblock) mu = self.shear_moduli[iblock] lame_lambda = self.lambda_moduli[iblock] rz = self.store.config.receiver_depth else: (rz, sz, firstx), (rz, sz, lastx), (nr, ns, nx) = \ self.get_block_extents(iblock) fc = self.store.config cc = copy.deepcopy(self.cc) cg = copy.deepcopy(self.cg) dx = fc.distance_delta logger.info( 'Starting step %i / %i, block %i / %i' % (self.step + 1, self.nsteps, iblock + 1, self.nblocks)) if self.step == 0: if cg.gf_depth_spacing is None: gf_depth_spacing = fc.source_depth_delta else: gf_depth_spacing = cg.gf_depth_spacing * km n_steps_depth = int((fc.source_depth_max - fc.source_depth_min) / gf_depth_spacing) + 1 if cg.gf_distance_spacing is None: gf_distance_spacing = fc.distance_delta else: gf_distance_spacing = cg.gf_distance_spacing * km n_steps_distance = int((fc.distance_max - fc.distance_min) / gf_distance_spacing) + 1 cg.depth_grid = PsGrnSpatialSampling( n_steps=n_steps_depth, start_distance=fc.source_depth_min / km, end_distance=fc.source_depth_max / km) cg.distance_grid = PsGrnSpatialSampling( n_steps=n_steps_distance, start_distance=fc.distance_min / km, end_distance=fc.distance_max / km) runner = PsGrnRunner(outdir=self.cg.psgrn_outdir) runner.run(cg, force=self.force) else: distances = num.linspace( firstx, firstx + (nx - 1) * dx, nx).tolist() # fomosto sample rate in s, pscmp takes days deltatdays = 1. / (fc.sample_rate * 24. * 3600.) cc.snapshots = PsCmpSnapshots( tmin=0., tmax=cg.max_time, deltatdays=deltatdays) cc.observation = PsCmpProfile( slat=0. - 0.001 * cake.m2d, slon=0.0, elat=0. + distances[-1] * cake.m2d, elon=0.0, n_steps=len(distances), distances=distances) runner = PsCmpRunner(keep_tmp=False) mtsize = float(dx * cc.rectangular_fault_size_factor) Ai = 1. / num.power(mtsize, 2) nullf, sf = get_iso_scaling_factors(mu=mu, lame_lambda=lame_lambda) mui = 1. / mu gfmapping = [ (('nn',), {'un': (0, Ai * sf), 'ud': (5, Ai * sf)}), (('ne',), {'ue': (3, 1 * Ai * mui)}), (('nd',), {'un': (1, 1 * Ai * mui), 'ud': (6, 1 * Ai * mui)}), (('ed',), {'ue': (4, 1 * Ai * mui)}), (('dd',), {'un': (2, Ai * sf), 'ud': (7, Ai * sf)}), (('ee',), {'un': (8, Ai * sf), 'ud': (9, Ai * sf)}), ] for mt, gfmap in gfmapping: cc.rectangular_source_patches = [] for idx in mt: pmt = PsCmpMomentTensor( lat=0. + 0.001 * dx * cake.m2d, lon=0.0, depth=float(sz), width=mtsize, length=mtsize, idx=idx) cc.rectangular_source_patches.extend( pmt.to_rfs(nullf)) runner.run(cc) 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) 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.warning( '%i insertions skipped (duplicates)' % duplicate_inserts) self.store.unlock() signal.signal(signal.SIGINT, original) if interrupted: raise KeyboardInterrupt() logger.info( 'Done with step %i / %i, block %i / %i' % ( self.step + 1, self.nsteps, iblock + 1, self.nblocks)) def init(store_dir, variant): if variant is None: variant = '2008a' if ('pscmp.' + variant) not in program_bins: raise gf.store.StoreError('unsupported pscmp variant: %s' % variant) if ('psgrn.' + variant) not in program_bins: raise gf.store.StoreError('unsupported psgrn variant: %s' % variant) c = PsGrnPsCmpConfig() store_id = os.path.basename(os.path.realpath(store_dir)) # Initialising a viscous mantle cake_mod = cake.load_model(fn=None, crust2_profile=(54., 23.)) mantle = cake_mod.material(z=45*km) mantle.burger_eta1 = 5e17 mantle.burger_eta2 = 1e19 mantle.burger_alpha = 1. config = gf.meta.ConfigTypeA( id=store_id, ncomponents=10, sample_rate=1. / (3600. * 24.), receiver_depth=0. * km, source_depth_min=0. * km, source_depth_max=15. * km, source_depth_delta=.5 * km, distance_min=0. * km, distance_max=50. * km, distance_delta=1. * km, earthmodel_1d=cake_mod, modelling_code_id='psgrn_pscmp.%s' % variant, tabulated_phases=[]) # dummy list c.validate() config.validate() return gf.store.Store.create_editables( store_dir, config=config, extra={'psgrn_pscmp': c}) def build(store_dir, force=False, nworkers=None, continue_=False, step=None, iblock=None): return PsGrnCmpGFBuilder.build( store_dir, force=force, nworkers=nworkers, continue_=continue_, step=step, iblock=iblock)