# http://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------
from __future__ import absolute_import, division
from builtins import zip
import math
import numpy as num
from . import trace
from . import ahfullgreen_ext as ext
class AhfullgreenError(Exception):
pass
def make_seismogram(
vp, vs, density, qp, qs, x, f, m6,
quantity, deltat, stf=None, wanted_components='ned',
want_far=True, want_intermediate=True, want_near=True,
npad_levelling=40, out_alignment=0.):
if stf is None:
stf = Impulse()
x = num.asarray(x, num.float)
f = num.asarray(f, num.float)
m6 = num.asarray(m6, num.float)
r = math.sqrt(num.sum(x**2))
tp = r / vp
ts = r / vs
if ts <= tp:
raise AhfullgreenError('unsupported material properties')
tpad = stf.t_cutoff() or deltat * 10.
tstart = tp - tpad - npad_levelling * deltat
tstart = out_alignment + round((tstart - out_alignment) / deltat) * deltat
nt = trace.nextpow2(int(math.ceil(
(ts - tp + 2 * tpad + 2*npad_levelling * deltat) / deltat)))
nspec = nt // 2 + 1
specs = []
for component in 'ned':
if component in wanted_components:
specs.append(num.zeros(nspec, dtype=num.complex))
else:
specs.append(None)
oc_c = {
'displacement': 1, # treated in post processing
'velocity': 1,
'acceleration': 2}[quantity]
out_spec_delta = float(2.0 * math.pi / (nt*deltat))
out_spec_offset = 0.0
omega = out_spec_offset + out_spec_delta * num.arange(nspec)
coeffs_stf = stf(omega/(2.*math.pi)).astype(num.complex)
coeffs_stf *= num.exp(1.0j * omega * tstart)
omega_max = 2.0 * math.pi * 0.5 / deltat
omega_cut = omega_max * 0.75
icut = int(num.ceil((omega_cut - out_spec_offset) / out_spec_delta))
coeffs_stf[icut:] *= 0.5 + 0.5 * num.cos(
math.pi * num.minimum(
1.0, (omega[icut:] - omega_cut) / (omega_max - omega_cut)))
ext.add_seismogram(
float(vp), float(vs), float(density), float(qp), float(qs),
x, f, m6, oc_c, out_spec_delta, out_spec_offset,
specs[0], specs[1], specs[2], want_far, want_intermediate, want_near)
outs = []
for i, component in enumerate('ned'):
if component not in wanted_components:
outs.append(None)
out = num.fft.irfft(coeffs_stf * specs[i], nt)
out /= deltat
assert out.size // 2 + 1 == specs[i].size
m1 = num.mean(
out[:npad_levelling] * num.linspace(1., 0., npad_levelling))
out -= m1 * 2.
if quantity == 'displacement':
out = num.cumsum(out) * deltat
outs.append(out)
outs_wanted = []
for component in wanted_components:
i = 'ned'.find(component)
if i != -1:
outs_wanted.append(outs[i])
else:
outs_wanted.append(None)
return tstart, outs_wanted
def add_seismogram(
vp, vs, density, qp, qs, x, f, m6,
quantity, deltat, out_offset,
out_n, out_e, out_d, stf=None,
want_far=True, want_intermediate=True, want_near=True,
npad_levelling=40):
ns = [out.size for out in (out_n, out_e, out_d) if out is not None]
if not all(n == ns[0] for n in ns):
raise AhfullgreenError('length of component arrays must be identical')
n = ns[0]
wanted_components = ''.join(
(c if out is not None else '-')
for (out, c) in zip((out_n, out_e, out_d), 'ned'))
tstart, temps = make_seismogram(
vp, vs, density, qp, qs, x, f, m6,
quantity, deltat, stf=stf,
wanted_components=wanted_components,
want_far=want_far,
want_intermediate=want_intermediate,
want_near=want_near,
npad_levelling=npad_levelling, out_alignment=out_offset)
for i, out in enumerate((out_n, out_e, out_d)):
if out is None:
continue
temp = temps[i]
ntemp = temp.size
tmin = max(out_offset, tstart)
tmax = min(
out_offset + (n-1) * deltat,
tstart + (ntemp-1) * deltat)
def ind(t, t0):
return int(round((t-t0)/deltat))
out[ind(tmin, out_offset):ind(tmax, out_offset)+1] \
+= temp[ind(tmin, tstart):ind(tmax, tstart)+1]
out[:ind(tmin, out_offset)] += 0.
out[ind(tmax, out_offset)+1:] += temp[ind(tmax, tstart)]
class Impulse(object):
def __init__(self):
pass
def t_cutoff(self):
return None
def __call__(self, f):
return num.ones(f.size, dtype=num.complex)
class Gauss(object):
def __init__(self, tau):
self._tau = tau
def t_cutoff(self):
return self._tau * 2.
def __call__(self, f):
omega = f * 2. * math.pi
return num.exp(-(omega**2 * self._tau**2 / 8.))
|