-
Notifications
You must be signed in to change notification settings - Fork 2
/
slowness_layer.py
337 lines (292 loc) · 13.8 KB
/
slowness_layer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Functions acting on slowness layers.
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from future.builtins import * # NOQA
import math
import numpy as np
from .c_wrappers import clibtau
from .helper_classes import SlownessLayer, SlownessModelError
from .velocity_layer import (evaluate_velocity_at_bottom,
evaluate_velocity_at_top)
def bullen_radial_slowness(layer, p, radius_of_planet, check=True):
"""
Calculate time and distance increments of a spherical ray.
The time and distance (in radians) increments accumulated by a ray of
spherical ray parameter p when passing through this layer. Note that this
gives half of the true range and time increments since there will be both
an upgoing and a downgoing path. Here we use the Mohorovicic or Bullen
law: p=A*r^B
The ``layer`` and ``p`` parameters must be either 0-D, or both of the same
shape.
:param layer: The layer(s) in which to calculate the increments.
:type layer: :class:`~numpy.ndarray`, dtype = :const:`SlownessLayer`
:param p: The spherical ray paramater to use for calculation, in s/km.
:type p: :class:`~numpy.ndarray`, dtype = :class:`float`
:param radius_of_planet: The radius of the planet to use, in km.
:type radius_of_planet: float
:param check: Check that the calculated results are not invalid. This
check may be disabled if the layers requested are expected not to
include the specified ray.
:type check: bool
:returns: Time (in s) and distance (in rad) increments.
:rtype: tuple of :class:`~numpy.ndarray`
"""
ldim = np.ndim(layer)
pdim = np.ndim(p)
if ldim == 1 and pdim == 0:
time = np.zeros(shape=layer.shape, dtype=np.float_)
dist = np.zeros(shape=layer.shape, dtype=np.float_)
elif ldim == 0 and pdim == 1:
time = np.zeros(shape=p.shape, dtype=np.float_)
dist = np.zeros(shape=p.shape, dtype=np.float_)
elif ldim == pdim and (ldim == 0 or layer.shape == p.shape):
time = np.zeros(shape=layer.shape, dtype=np.float_)
dist = np.zeros(shape=layer.shape, dtype=np.float_)
else:
raise TypeError('Either layer or p must be 0D, or they must have '
'the same shape.')
length = len(time)
if isinstance(p, np.float_):
p = p * np.ones(length, dtype=np.float_)
clibtau.bullen_radial_slowness_inner_loop(
layer, p, time, dist, radius_of_planet, length)
if check and (np.any(time < 0) or np.any(np.isnan(time)) or
np.any(dist < 0) or np.any(np.isnan(dist))):
raise SlownessModelError("timedist.time or .dist < 0 or Nan")
return time, dist
def bullen_depth_for(layer, ray_param, radius_of_planet, check=True):
"""
Finds the depth for a ray parameter within this layer.
Uses a Bullen interpolant, Ar^B. Special case for ``bot_p == 0`` or
``bot_depth == radius_of_planet`` as these cause division by 0; use linear
interpolation in this case.
The ``layer`` and ``ray_param`` parameters must be either 0-D, or both of
the same shape.
:param layer: The layer(s) to check.
:type layer: :class:`~numpy.ndarray` (shape = (1, ), dtype =
:const:`SlownessLayer`)
:param ray_param: The ray parameter(s) to use for calculation, in s/km.
:type ray_param: float
:param radius_of_planet: The radius (in km) of the planet to use.
:type radius_of_planet: float
:returns: The depth (in km) for the specified ray parameter.
:rtype: float
"""
ldim = np.ndim(layer)
pdim = np.ndim(ray_param)
if ldim == 1 and pdim == 0:
ray_param = ray_param * np.ones(layer.shape, dtype=np.float_)
depth = np.zeros(shape=layer.shape, dtype=np.float_)
elif ldim == 0 and pdim == 1:
layer = layer * np.ones(ray_param.shape, dtype=SlownessLayer)
depth = np.zeros(shape=ray_param.shape, dtype=np.float_)
elif ldim == pdim and (ldim == 0 or layer.shape == ray_param.shape):
if ldim == 0:
# Make array-like to work with NumPy < 1.9.
layer = np.array([layer], dtype=SlownessLayer)
ray_param = np.array([ray_param])
depth = np.zeros(shape=layer.shape, dtype=np.float_)
else:
raise TypeError('Either layer or ray_param must be 0D, or they must '
'have the same shape.')
valid = (layer['top_p'] - ray_param) * (ray_param - layer['bot_p']) >= 0
if not check or np.all(valid):
leftover = np.ones_like(depth, dtype=np.bool_)
# Easy cases for 0 thickness layer, or ray parameter found at
# top or bottom.
mask = layer['top_depth'] == layer['bot_depth']
depth[mask] = layer['bot_depth'][mask]
leftover &= ~mask
mask = leftover & (layer['top_p'] == ray_param)
depth[mask] = layer['top_depth'][mask]
leftover &= ~mask
mask = leftover & (layer['bot_p'] == ray_param)
depth[mask] = layer['bot_depth'][mask]
leftover &= ~mask
mask = leftover & (
(layer['bot_p'] != 0) & (layer['bot_depth'] != radius_of_planet))
if np.any(mask):
top_p_mask = layer['top_p'][mask]
bot_p_mask = layer['bot_p'][mask]
top_depth_mask = layer['top_depth'][mask]
bot_depth_mask = layer['bot_depth'][mask]
ray_param_mask = ray_param[mask]
b = np.divide(np.log(top_p_mask / bot_p_mask),
np.log((radius_of_planet - top_depth_mask) /
(radius_of_planet - bot_depth_mask)))
with np.errstate(over='ignore'):
denom = np.power(radius_of_planet - top_depth_mask, b)
a = np.divide(top_p_mask, denom)
temp_depth = np.empty_like(a)
mask2 = (a != 0) & (b != 0)
temp_depth[mask2] = radius_of_planet - np.exp(
1.0 / b[mask2] * np.log(np.divide(ray_param_mask[mask2],
a[mask2])))
# or equivalent (maybe better stability?):
# tempDepth = radius_of_planet - math.pow(ray_param_mask/A, 1/B)
# Overflow. Use linear interpolation.
temp_depth[~mask2] = (
(bot_depth_mask[~mask2] - top_depth_mask[~mask2]) /
(bot_p_mask[~mask2] - top_p_mask[~mask2]) *
(ray_param_mask[~mask2] - top_p_mask[~mask2])
) + top_depth_mask[~mask2]
# Check if slightly outside layer due to rounding or
# numerical instability:
mask2 = ((top_depth_mask > temp_depth) &
(temp_depth > top_depth_mask - 0.000001))
temp_depth[mask2] = top_depth_mask[mask2]
mask2 = ((bot_depth_mask < temp_depth) &
(temp_depth < bot_depth_mask + 0.000001))
temp_depth[mask2] = bot_depth_mask[mask2]
mask2 = ((temp_depth < 0) | np.isnan(temp_depth) |
np.isinf(temp_depth) |
(temp_depth < top_depth_mask) |
(temp_depth > bot_depth_mask))
# Numerical instability in power law calculation? Try a
# linear interpolation if the layer is small (<5km).
small_layer = bot_depth_mask[mask2] - top_depth_mask[mask2] > 5
if np.any(small_layer):
if check:
raise SlownessModelError(
"Calculated depth is outside layer, negative, or NaN.")
else:
temp_depth[mask2][small_layer] = np.nan
linear = (
(bot_depth_mask[mask2] - top_depth_mask[mask2]) /
(bot_p_mask[mask2] - top_p_mask[mask2]) *
(ray_param_mask[mask2] - top_p_mask[mask2])
) + top_depth_mask[mask2]
outside_layer = small_layer & (
linear < 0 | np.isnan(linear) | np.isinf(linear))
if np.any(outside_layer):
if check:
raise SlownessModelError(
"Calculated depth is outside layer, negative, or NaN.")
else:
temp_depth[mask2][outside_layer] = np.nan
temp_depth[mask2] = linear
# Check for tempDepth just above top_depth or below bottomDepth.
mask2 = ((temp_depth < top_depth_mask) &
(top_depth_mask - temp_depth < 1e-10))
temp_depth[mask2] = top_depth_mask[mask2]
mask2 = ((temp_depth > bot_depth_mask) &
(temp_depth - bot_depth_mask < 1e-10))
temp_depth[mask2] = bot_depth_mask[mask2]
depth[mask] = temp_depth
leftover &= ~mask
# Special case for the centre of the planet, since Ar^B might
# blow up at r = 0.
mask = leftover & (layer['top_p'] != layer['bot_p'])
depth[mask] = (layer['bot_depth'][mask] +
(ray_param[mask] - layer['bot_p'][mask]) *
(layer['top_depth'][mask] - layer['bot_depth'][mask]) /
(layer['top_p'][mask] - layer['bot_p'][mask]))
leftover &= ~mask
# weird case, return bot_depth??
depth[leftover] = layer['bot_depth'][leftover]
# Make sure invalid cases are left out
depth[~valid] = np.nan
# NumPy < 1.9 compatibility.
if ldim == 0 and pdim == 0:
return depth[0]
else:
return depth
else:
raise SlownessModelError(
"Ray parameter is not contained within this slowness layer.")
def evaluate_at_bullen(layer, depth, radius_of_planet):
"""
Find the slowness at the given depth.
Note that this method assumes a Bullen type of slowness interpolation,
i.e., p(r) = a*r^b. This will produce results consistent with a tau model
that uses this interpolant, but it may differ slightly from going directly
to the velocity model. Also, if the tau model is generated using another
interpolant, linear for instance, then the result may not be consistent
with the tau model.
:param layer: The layer to use for the calculation.
:type layer: :class:`numpy.ndarray`, dtype = :const:`SlownessLayer`
:param depth: The depth (in km) to use for the calculation. It must be
contained within the provided ``layer`` or else results are undefined.
:type depth: float
:param radius_of_planet: The radius of the planet to use, in km.
:type radius_of_planet: float
"""
top_p = layer['top_p']
bot_p = layer['bot_p']
top_depth = layer['top_depth']
bot_depth = layer['bot_depth']
# Could do some safeguard asserts...
assert not bot_depth > radius_of_planet
assert not (top_depth - depth) * (depth - bot_depth) < 0
if depth == top_depth:
return top_p
elif depth == bot_depth:
return bot_p
else:
try:
# The power law calculation has some stability issues with very
# small layers. Catch them here to trigger the fallback
# computations later on.
with np.errstate(all="raise"):
b = math.log(top_p / bot_p) / \
math.log((radius_of_planet - top_depth) /
(radius_of_planet - bot_depth))
a_denominator = pow((radius_of_planet - top_depth), b)
a = top_p / a_denominator
answer = a * pow((radius_of_planet - depth), b)
except FloatingPointError:
answer = np.nan
if answer < 0 or math.isnan(answer) or math.isinf(answer):
# numerical instability in power law calculation???
# try a linear interpolation if the layer is small ( <2 km)
# or if denominator of A is infinity as we probably overflowed
# the double in that case.
if bot_depth - top_depth < 2 \
or math.isinf(a_denominator) \
or bot_p == 0:
linear = (bot_p - top_p) / (bot_depth - top_depth) * \
(depth - top_depth) + top_p
if linear < 0 \
or math.isinf(linear) \
or math.isnan(linear):
pass
else:
return linear
raise SlownessModelError(
"Calculated Slowness is NaN or negative!")
return answer
def create_from_vlayer(v_layer, is_p_wave, radius_of_planet,
is_spherical=True):
"""
Compute the slowness layer from a velocity layer.
:param v_layer: The velocity layer to convert.
:type v_layer: :class:`numpy.ndarray`, dtype = :const:`VelocityLayer`
:param is_p_wave: Whether this velocity layer is for compressional/P
(``True``) or shear/S (``False``) waves.
:type is_p_wave: bool
:param radius_of_planet: The radius of the planet to use, in km.
:type radius_of_planet: float
:param is_spherical: Whether the model is spherical. Non-spherical models
are not currently supported.
:type is_spherical: bool
"""
ret = np.empty(shape=v_layer.shape, dtype=SlownessLayer)
ret['top_depth'] = v_layer['top_depth']
ret['bot_depth'] = v_layer['bot_depth']
wave_type = ('p' if is_p_wave else 's')
if is_spherical:
ret['top_p'] = (radius_of_planet - ret['top_depth']) / \
evaluate_velocity_at_top(v_layer, wave_type)
bot_depth = ret["bot_depth"]
bot_vel = evaluate_velocity_at_bottom(v_layer, wave_type)
if bot_depth.shape:
if bot_depth[-1] == radius_of_planet and bot_vel[-1] == 0.0:
bot_depth[-1] = 1.0
ret['bot_p'] = (radius_of_planet - bot_depth) / bot_vel
else:
raise NotImplementedError("no flat models yet")
return ret