shipgrav 1.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- shipgrav/__init__.py +147 -0
- shipgrav/database.toml +24 -0
- shipgrav/grav.py +1260 -0
- shipgrav/io.py +749 -0
- shipgrav/nav.py +91 -0
- shipgrav/tests/__init__.py +16 -0
- shipgrav/tests/__main__.py +3 -0
- shipgrav/tests/ex_files/AT05_01_bgm.RGS +4 -0
- shipgrav/tests/ex_files/DGStest_laptop.dat +1001 -0
- shipgrav/tests/ex_files/IXBlue.yaml +160 -0
- shipgrav/tests/ex_files/SR2312_dgs_raw.txt +2 -0
- shipgrav/tests/ex_files/SR2312_mru.txt +12 -0
- shipgrav/tests/ex_files/TN400_bgm.Raw +2 -0
- shipgrav/tests/ex_files/TN400_nav.Raw +2 -0
- shipgrav/tests/test_grav_data.py +75 -0
- shipgrav/tests/test_grav_nodata.py +47 -0
- shipgrav/tests/test_io.py +55 -0
- shipgrav/tests/test_nav.py +41 -0
- shipgrav/tests/test_utils.py +33 -0
- shipgrav/utils.py +275 -0
- shipgrav-1.0.0.dist-info/METADATA +44 -0
- shipgrav-1.0.0.dist-info/RECORD +24 -0
- shipgrav-1.0.0.dist-info/WHEEL +5 -0
- shipgrav-1.0.0.dist-info/licenses/LICENSE +675 -0
shipgrav/grav.py
ADDED
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.signal import lfilter, firwin
|
|
3
|
+
from pandas import DataFrame
|
|
4
|
+
from statsmodels.api import OLS, add_constant
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from math import factorial
|
|
7
|
+
from scipy.special import erf, erfcinv
|
|
8
|
+
from copy import copy
|
|
9
|
+
|
|
10
|
+
# impulse response of 10th order Taylor series differentiator
|
|
11
|
+
tay10 = [1/1260, -5/504, 5/84, -5/21, 5/6,
|
|
12
|
+
0, -5/6, 5/21, -5/84, 5/504, -1/1260]
|
|
13
|
+
|
|
14
|
+
########################################################################
|
|
15
|
+
# tidal correction
|
|
16
|
+
########################################################################
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _convert_datetime_tidetime(timestamp):
|
|
20
|
+
"""Calculate julian century and hour from a timestamp.
|
|
21
|
+
|
|
22
|
+
The reference point is noon on December 31, 1899 per Longman's paper.
|
|
23
|
+
|
|
24
|
+
:param timestamp: time to convert to century + hour
|
|
25
|
+
:type timestamp: datetime.datetime
|
|
26
|
+
|
|
27
|
+
:returns:
|
|
28
|
+
- **julian century** (*int*) - centuries since reference date, days/36525
|
|
29
|
+
- **julian_hour** (*float*) - hours since midnight
|
|
30
|
+
"""
|
|
31
|
+
origin_time = datetime(1899, 12, 31, 12, 0, 0, tzinfo=timezone.utc)
|
|
32
|
+
dt = timestamp - origin_time
|
|
33
|
+
days = dt.days + dt.seconds/3600./24.
|
|
34
|
+
julian_century = days/36525
|
|
35
|
+
julian_hour = (timestamp.hour + timestamp.minute /
|
|
36
|
+
60. + timestamp.second/3600.)
|
|
37
|
+
|
|
38
|
+
return julian_century, julian_hour
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def longman_tide_prediction(lon, lat, times, alt=0, return_components=False):
|
|
42
|
+
""" Calculate predicted tidal effect on gravity using the Longman algorithm.
|
|
43
|
+
|
|
44
|
+
The calculation is taken directly from
|
|
45
|
+
|
|
46
|
+
Longman (1959). Formulas for Computing the Tidal Accelerations Due to
|
|
47
|
+
the Moon and the Sun. Journal of Geophysical Research 64(12),
|
|
48
|
+
DOI: 10.1029/JZ064i012p02351
|
|
49
|
+
|
|
50
|
+
as are all of the constant and variable descriptions.
|
|
51
|
+
Tidal contribution(s) returned are in mGal.
|
|
52
|
+
|
|
53
|
+
:param lon: longitude in decimal degrees, positive E
|
|
54
|
+
:type lon: array_like
|
|
55
|
+
:param lat: latitude in decimal degrees, positive N
|
|
56
|
+
:type lat: array_like
|
|
57
|
+
:param times: times for geographic locations
|
|
58
|
+
:type times: array_like, datetime.datetime
|
|
59
|
+
:param alt: altitude in meters (0 for sea level, for marine grav)
|
|
60
|
+
:type alt: array_like
|
|
61
|
+
:param return_components: if True, return lunar and solar components with total.
|
|
62
|
+
:type return_components: bool
|
|
63
|
+
|
|
64
|
+
:returns:
|
|
65
|
+
- **g0** (*ndarray*)- total tidal effect in mGal
|
|
66
|
+
- **gm** (*ndarray*)- lunar tidal effect in mGal (optional, if return_components is True)
|
|
67
|
+
- **gs** (*ndarray*)- solar tidal effect in mGal (optional, if return_components is True)
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
assert len(lon) == len(lat), 'lengths of input vectors must be the same'
|
|
71
|
+
assert len(lon) == len(times), 'lengths of input vectors must be the same'
|
|
72
|
+
|
|
73
|
+
# convert the timestamps, referenced to Longman origin time
|
|
74
|
+
T, t0 = np.empty(len(times)), np.empty(len(times))
|
|
75
|
+
for i, stamp in enumerate(times):
|
|
76
|
+
a, b = _convert_datetime_tidetime(stamp)
|
|
77
|
+
T[i] = a
|
|
78
|
+
t0[i] = b
|
|
79
|
+
if t0[i] < 0:
|
|
80
|
+
t0[i] += 24
|
|
81
|
+
if t0[i] >= 24:
|
|
82
|
+
t0[i] == 24
|
|
83
|
+
|
|
84
|
+
TT = T*T # squares and cubes are used a lot so shortcut them
|
|
85
|
+
TTT = T*T*T
|
|
86
|
+
|
|
87
|
+
# define a bunch of constants for the calculation
|
|
88
|
+
mu = 6.673e-8 # Newton's gravitational constant
|
|
89
|
+
M = 7.3537e25 # Mass of the moon in grams
|
|
90
|
+
S = 1.993e33 # Mass of the sun in grams
|
|
91
|
+
e = 0.05490 # Eccentricity of the moon's orbit
|
|
92
|
+
m = 0.074804 # Ratio of mean motion of the sun to that of the moon
|
|
93
|
+
c = 3.84402e10 # Mean distance between the centers of the earth and the moon
|
|
94
|
+
c1 = 1.495e13 # Mean distance between centers of the earth and sun in cm
|
|
95
|
+
h2 = 0.612 # Love parameter
|
|
96
|
+
k2 = 0.303 # Love parameter
|
|
97
|
+
a = 6.378270e8 # Earth's equitorial radius in cm
|
|
98
|
+
i = 0.08979719 # (i) Inclination of the moon's orbit to the ecliptic
|
|
99
|
+
# Inclination of the Earth's equator to the ecliptic 23.452 degrees
|
|
100
|
+
omega = np.radians(23.452)
|
|
101
|
+
lamb = np.radians(lat) # (lambda) Latitude of point
|
|
102
|
+
H = alt * 100. # (H) Altitude above sea-level of point P in cm
|
|
103
|
+
|
|
104
|
+
# Longman convention has W+/E- for unknown reasons
|
|
105
|
+
L = -lon
|
|
106
|
+
|
|
107
|
+
# Lunar gravity effects (Shureman 1941 coeffs)
|
|
108
|
+
# mean longitude of the moon in its orbit, reckoned from the referred equinox
|
|
109
|
+
s = 4.720023434 + 8399.709299*T + 0.0000440696*TT # skip 3rd order bc it is tiny
|
|
110
|
+
# mean longitude of lunar perigee
|
|
111
|
+
p = 5.835124713 + 71.01800936*T - 0.000180545*TT - 0.00000021817*TTT
|
|
112
|
+
# mean longitude of the sun
|
|
113
|
+
h = 4.88162792 + 628.3319509*T + 0.00000527962*TT
|
|
114
|
+
# longitude of moon's ascending node in orbit reckoned from referred equinox
|
|
115
|
+
N = 4.523588564 - 33.75715303*T + 0.000036749*TT # skip 3rd order bc it is tiny
|
|
116
|
+
|
|
117
|
+
# inclination of the moon's orbit to the equator
|
|
118
|
+
I = np.arccos(np.cos(omega)*np.cos(i) - np.sin(omega)*np.sin(i)*np.cos(N))
|
|
119
|
+
# longitude in the celestial equator of its intersection A with the moon's orbit
|
|
120
|
+
nu = np.arcsin(np.sin(i)*np.sin(N)/np.sin(I))
|
|
121
|
+
# hour angle of mean sun measured westward from the place of observations
|
|
122
|
+
t = np.radians(15. * (t0 - 12) - L)
|
|
123
|
+
|
|
124
|
+
# right ascension of meridian of place of observations reckoned from A
|
|
125
|
+
chi = t + h - nu
|
|
126
|
+
# cos(alpha) where alpha is defined in eq. 15 and 16 of Longman 1959
|
|
127
|
+
cos_alpha = np.cos(N)*np.cos(nu) + np.sin(N)*np.sin(nu)*np.cos(omega)
|
|
128
|
+
# sin(alpha) where alpha is defined in eq. 15 and 16 of Longman 1959
|
|
129
|
+
sin_alpha = np.sin(omega)*np.sin(N)/np.sin(I)
|
|
130
|
+
# alpha from eq. 18 of Longman 1959
|
|
131
|
+
alpha = 2*np.arctan(sin_alpha/(1 + cos_alpha))
|
|
132
|
+
# longitude in the moon's orbit of its ascending intersection with the celestial equator
|
|
133
|
+
xi = N - alpha
|
|
134
|
+
|
|
135
|
+
# mean longitude of moon in radians in its orbit reckoned from A
|
|
136
|
+
sigma = s - xi
|
|
137
|
+
# longitude of moon in its orbit reckoned from its ascending intersection with the equator
|
|
138
|
+
L_moon = (sigma + 2*e*np.sin(s - p) + (5./4)*e*e*np.sin(2*(s - p)) +
|
|
139
|
+
(15./4)*m*e*np.sin(s - 2*h + p) + (11./8)*m*m*np.sin(2*(s - h)))
|
|
140
|
+
|
|
141
|
+
# Solar calculations
|
|
142
|
+
# mean longitude of solar perigee
|
|
143
|
+
p1 = 4.908229461 + 0.03000526416*T + 0.000007902463*TT # skip tiny 3rd order term
|
|
144
|
+
# eccentricity of Earth's orbit
|
|
145
|
+
e1 = 0.01675104 - 0.0000418*T - 0.000000126*TT
|
|
146
|
+
|
|
147
|
+
# right ascension of meridian of place of observations reckoned from the vernal equinox
|
|
148
|
+
chi1 = t + h
|
|
149
|
+
# longitude of sun in the ecliptic reckoned from the vernal equinox
|
|
150
|
+
L_sun = h + 2*e1*np.sin(h - p1)
|
|
151
|
+
# cosine(theta) where theta is the zenith angle of the moon
|
|
152
|
+
cos_theta = (np.sin(lamb)*np.sin(I)*np.sin(L_moon) + np.cos(lamb)*(np.cos(0.5*I)**2
|
|
153
|
+
* np.cos(L_moon - chi) + np.sin(0.5*I)**2*np.cos(L_moon + chi)))
|
|
154
|
+
# cosine(phi) where phi is the zenith angle of the sun
|
|
155
|
+
cos_phi = (np.sin(lamb)*np.sin(omega)*np.sin(L_sun) + np.cos(lamb) *
|
|
156
|
+
(np.cos(0.5*omega)**2*np.cos(L_sun - chi1) +
|
|
157
|
+
np.sin(0.5*omega)**2*np.cos(L_sun + chi1)))
|
|
158
|
+
|
|
159
|
+
# Distance
|
|
160
|
+
# distance parameter, eq. 34 in Longman 1959
|
|
161
|
+
C = np.sqrt(1./(1 + 0.006738*np.sin(lamb)**2))
|
|
162
|
+
# distance from point P to the center of the Earth
|
|
163
|
+
r = C*a + H
|
|
164
|
+
# distance parameter, eq. 31 in Longman 1959
|
|
165
|
+
aprime = 1. / (c * (1 - e * e))
|
|
166
|
+
# distance parameter, eq. 32 in Longman 1959
|
|
167
|
+
aprime1 = 1. / (c1 * (1 - e1 * e1))
|
|
168
|
+
# distance between centers of the Earth and the moon
|
|
169
|
+
d = (1./((1./c) + aprime*e*np.cos(s - p) + aprime*e*e *
|
|
170
|
+
np.cos(2*(s - p)) + (15./8)*aprime*m*e*np.cos(s - 2*h + p)
|
|
171
|
+
+ aprime*m*m*np.cos(2*(s - h))))
|
|
172
|
+
# distance between centers of the Earth and the sun
|
|
173
|
+
D = 1./((1./c1) + aprime1*e1*np.cos(h - p1))
|
|
174
|
+
|
|
175
|
+
# vertical component of tidal acceleration due to the moon
|
|
176
|
+
gm = ((mu*M*r/(d*d*d))*(3*cos_theta**2 - 1) + (3./2) *
|
|
177
|
+
(mu*M*r*r/(d*d*d*d))*(5*cos_theta**3 - 3*cos_theta))
|
|
178
|
+
# vertical component of tidal acceleration due to the sun
|
|
179
|
+
gs = mu*S*r/(D*D*D)*(3*cos_phi**2 - 1)
|
|
180
|
+
|
|
181
|
+
love = (1 + h2 - 1.5*k2)
|
|
182
|
+
g0 = (gm + gs) * 1e3*love
|
|
183
|
+
if return_components:
|
|
184
|
+
return g0, gm*1e3*love, gs*1e3*love
|
|
185
|
+
else:
|
|
186
|
+
return g0
|
|
187
|
+
|
|
188
|
+
########################################################################
|
|
189
|
+
# Eotvos correction
|
|
190
|
+
########################################################################
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def eotvos_full(lon, lat, ht, samp, a=6378137.0, b=6356752.3142):
|
|
194
|
+
""" Full Eotvos correction in mGals.
|
|
195
|
+
|
|
196
|
+
The Eotvos correction is the effect on measured gravity due to horizontal
|
|
197
|
+
motion over the Earth's surface.
|
|
198
|
+
|
|
199
|
+
This formulation is from Harlan (1968), "Eotvos Corrections for Airborne Gravimetry" in
|
|
200
|
+
*Journal of Geophysical Research*, 73(14), DOI: 10.1029/JB073i014p04675
|
|
201
|
+
|
|
202
|
+
Implementation modified from matlab script written by Sandra Preaux, NGS, NOAA August 24 2009
|
|
203
|
+
|
|
204
|
+
Components of the correction:
|
|
205
|
+
|
|
206
|
+
* rdoubledot
|
|
207
|
+
* angular acceleration of the reference frame
|
|
208
|
+
* corliolis
|
|
209
|
+
* centrifugal
|
|
210
|
+
* centrifugal acceleration of Earth
|
|
211
|
+
|
|
212
|
+
:param lon: longitudes in degrees
|
|
213
|
+
:type lon: array_like
|
|
214
|
+
:param lat: latitudes in degrees
|
|
215
|
+
:type lat: array_like
|
|
216
|
+
:param ht: elevation (referenced to sea level)
|
|
217
|
+
:type ht: array_like
|
|
218
|
+
:param samp: sampling rate
|
|
219
|
+
:type samp: float
|
|
220
|
+
:param a: major axis of ellipsoid (default is WGS84)
|
|
221
|
+
:type a: float, optional
|
|
222
|
+
:param b: minor axis of ellipsoid (default is WGS84)
|
|
223
|
+
:type b: float, optional
|
|
224
|
+
|
|
225
|
+
:return: **E** (*ndarray*), Eotvos correction in mGal
|
|
226
|
+
"""
|
|
227
|
+
We = 0.00007292115 # siderial rotation rate, radians/sec
|
|
228
|
+
mps2mgal = 100000 # m/s/s to mgal
|
|
229
|
+
ecc = (a-b)/a
|
|
230
|
+
|
|
231
|
+
latr = np.deg2rad(lat)
|
|
232
|
+
lonr = np.deg2rad(lon)
|
|
233
|
+
|
|
234
|
+
# get time derivatives of position
|
|
235
|
+
dlat = center_diff(latr, 1, samp)
|
|
236
|
+
ddlat = center_diff(latr, 2, samp)
|
|
237
|
+
dlon = center_diff(lonr, 1, samp)
|
|
238
|
+
ddlon = center_diff(lonr, 2, samp)
|
|
239
|
+
dht = center_diff(ht, 1, samp)
|
|
240
|
+
ddht = center_diff(ht, 2, samp)
|
|
241
|
+
|
|
242
|
+
# sines and cosines etc
|
|
243
|
+
slat = np.sin(latr[1:-1])
|
|
244
|
+
clat = np.cos(latr[1:-1])
|
|
245
|
+
s2lat = np.sin(2*latr[1:-1])
|
|
246
|
+
c2lat = np.cos(2*latr[1:-1])
|
|
247
|
+
|
|
248
|
+
# calculate r' and its derivatives
|
|
249
|
+
rp = a*(1 - ecc*slat*slat)
|
|
250
|
+
drp = -a*dlat*ecc*s2lat
|
|
251
|
+
ddrp = -a*ddlat*ecc*s2lat - 2*a*dlat*dlat*ecc*c2lat
|
|
252
|
+
|
|
253
|
+
# calculate deviation from normal and derivatives
|
|
254
|
+
D = np.arctan(ecc*s2lat)
|
|
255
|
+
dD = 2*dlat*ecc*c2lat
|
|
256
|
+
ddD = 2*ddlat*ecc*c2lat - 4*dlat*dlat*ecc*s2lat
|
|
257
|
+
|
|
258
|
+
# define r and its derivatives
|
|
259
|
+
r = np.vstack((-rp*np.sin(D), np.zeros(len(rp)), -
|
|
260
|
+
rp*np.cos(D) - ht[1:-1])).T
|
|
261
|
+
rdot = np.vstack((-drp*np.sin(D) - rp*dD*np.cos(D),
|
|
262
|
+
np.zeros(len(rp)), -drp*np.cos(D) + rp*dD*np.sin(D) - dht)).T
|
|
263
|
+
ci = -ddrp*np.sin(D) - 2.*drp*dD*np.cos(D) - rp * \
|
|
264
|
+
(ddD*np.cos(D) - dD*dD*np.sin(D))
|
|
265
|
+
ck = -ddrp*np.cos(D) + 2.*drp*dD*np.sin(D) + rp * \
|
|
266
|
+
(ddD*np.sin(D) + dD*dD*np.cos(D) - ddht)
|
|
267
|
+
rdotdot = np.vstack((ci, np.zeros(len(ci)), ck)).T
|
|
268
|
+
|
|
269
|
+
# define w and derivative
|
|
270
|
+
w = np.vstack(((dlon + We)*clat, -dlat, -(dlon + We)*slat)).T
|
|
271
|
+
wdot = np.vstack((dlon*clat - (dlon + We)*dlat*slat, -
|
|
272
|
+
ddlat, -ddlon*slat - (dlon + We)*dlat*clat)).T
|
|
273
|
+
|
|
274
|
+
w2xrdot = np.cross(2*w, rdot)
|
|
275
|
+
wdotxr = np.cross(wdot, r)
|
|
276
|
+
wxr = np.cross(w, r)
|
|
277
|
+
wxwxr = np.cross(w, wxr)
|
|
278
|
+
|
|
279
|
+
# calcualte wexwexre, centrifugal acceleration due to the Earth
|
|
280
|
+
re = np.vstack((-rp*np.sin(D), np.zeros(len(rp)), -rp*np.cos(D))).T
|
|
281
|
+
we = np.vstack((We*clat, np.zeros(len(slat)), -We*slat)).T
|
|
282
|
+
wexre = np.cross(we, re)
|
|
283
|
+
wexwexre = np.cross(we, wexre)
|
|
284
|
+
wexr = np.cross(we, r)
|
|
285
|
+
wexwexr = np.cross(we, wexr)
|
|
286
|
+
|
|
287
|
+
# calculate total acceleration for the aircraft
|
|
288
|
+
a = rdotdot + w2xrdot + wdotxr + wxwxr
|
|
289
|
+
|
|
290
|
+
# Eotvos correction is the vertical component of the total acceleraton of
|
|
291
|
+
# the aircraft minus the centrifugal acceleration of the Earth, convert to mGal
|
|
292
|
+
E = (a[:, 2] - wexwexr[:, 2])*mps2mgal
|
|
293
|
+
E = np.hstack((E[0], E, E[-1]))
|
|
294
|
+
|
|
295
|
+
return E
|
|
296
|
+
|
|
297
|
+
########################################################################
|
|
298
|
+
# latitude correction functions
|
|
299
|
+
########################################################################
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def free_air_second_order(lat, ht):
|
|
303
|
+
""" 2nd order free-air correction
|
|
304
|
+
|
|
305
|
+
:param lat: latitude, degrees
|
|
306
|
+
:type lat: array_like
|
|
307
|
+
:param height: elevation, meters
|
|
308
|
+
:type height: array_like
|
|
309
|
+
|
|
310
|
+
:return: free-air correction, mGal
|
|
311
|
+
"""
|
|
312
|
+
s2lat = np.sin(np.deg2rad(lat))**2
|
|
313
|
+
|
|
314
|
+
return -((0.3087691 - 0.0004398*s2lat)*ht) + 7.2125e-8*(ht**2)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def wgs_grav(lat):
|
|
318
|
+
""" Theoretical gravity for WGS84 ellipsoid
|
|
319
|
+
|
|
320
|
+
:param lat: latitude, degrees
|
|
321
|
+
:type lat: array_like
|
|
322
|
+
|
|
323
|
+
:return: uniform ellipsoid gravity, mGal
|
|
324
|
+
"""
|
|
325
|
+
sinsq = np.sin(np.deg2rad(lat))**2
|
|
326
|
+
|
|
327
|
+
num = 1 + 0.00193185265241*sinsq # something like (b*gp - a*ge)/(a*ge)
|
|
328
|
+
den = np.sqrt(1 - 0.00669437999014*sinsq) # this is e2
|
|
329
|
+
return 978032.53359*(num/den)
|
|
330
|
+
|
|
331
|
+
########################################################################
|
|
332
|
+
# cross-coupling coefficients
|
|
333
|
+
########################################################################
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def calc_cross_coupling_coefficients(faa_in, vcc_in, ve_in, al_in, ax_in, level_in, times=None, samplerate=1):
|
|
337
|
+
""" Calculate cross-coupling coefficients from FAA via ordinary linear regression
|
|
338
|
+
|
|
339
|
+
The cross-coupling coefficients are returned in `model`, in the `params` attribute
|
|
340
|
+
|
|
341
|
+
:param faa_in: free air anomaly, filtered
|
|
342
|
+
:type faa_in: array_like
|
|
343
|
+
:param vcc_in: vcc monitor
|
|
344
|
+
:type vcc_in: array_like
|
|
345
|
+
:param ve_in: ve monitor
|
|
346
|
+
:type ve_in: array_like
|
|
347
|
+
:param al_in: al monitor
|
|
348
|
+
:type al_in: array_like
|
|
349
|
+
:param ax_in: ax monitor
|
|
350
|
+
:type ax_in: array_like
|
|
351
|
+
:param level_in: tilt/leveling correction, often negligible.
|
|
352
|
+
Use a vector of zeros to ignore this component.
|
|
353
|
+
:type level_in: array_like
|
|
354
|
+
:param times: timestamps to use for dividing the data into continuous sections
|
|
355
|
+
:type times: array_like, floats, optional
|
|
356
|
+
:param samplerate: used with times to detect large sampling gaps in the data
|
|
357
|
+
:type samplerate: float, optional
|
|
358
|
+
|
|
359
|
+
:return:
|
|
360
|
+
- **df** (*pd.DataFrame*)- double-differenced and filtered monitors and gravity
|
|
361
|
+
- **model** (*statsmodels.OLS*)- linear regression model
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
end_inds = np.array([len(faa_in),]) # just the one
|
|
365
|
+
if times is not None: # supplied a vector of timestamps to go with everything else
|
|
366
|
+
# split data into segments of continuous even sample rate
|
|
367
|
+
tdiff = np.diff(times)
|
|
368
|
+
if np.any(tdiff > 5*samplerate):
|
|
369
|
+
# 5 sec gap doesn't matter much I hope???
|
|
370
|
+
end_inds = np.where(tdiff > 5*samplerate)[0]
|
|
371
|
+
|
|
372
|
+
end_inds = np.append(-1, end_inds) # add starting point
|
|
373
|
+
|
|
374
|
+
faa_tf = np.array([])
|
|
375
|
+
vcc_tf = np.array([])
|
|
376
|
+
ve_tf = np.array([])
|
|
377
|
+
al_tf = np.array([])
|
|
378
|
+
ax_tf = np.array([])
|
|
379
|
+
le_tf = np.array([]) # things to fit
|
|
380
|
+
|
|
381
|
+
for i in range(1, len(end_inds)): # loop continuous-time segments
|
|
382
|
+
faa = faa_in[end_inds[i-1]+1:end_inds[i]]
|
|
383
|
+
vcc = vcc_in[end_inds[i-1]+1:end_inds[i]]
|
|
384
|
+
ve = ve_in[end_inds[i-1]+1:end_inds[i]]
|
|
385
|
+
al = al_in[end_inds[i-1]+1:end_inds[i]]
|
|
386
|
+
ax = ax_in[end_inds[i-1]+1:end_inds[i]]
|
|
387
|
+
level = level_in[end_inds[i-1]+1:end_inds[i]]
|
|
388
|
+
if len(faa) < 1000:
|
|
389
|
+
continue # no point for very short segments
|
|
390
|
+
# double derivatives with taylor series
|
|
391
|
+
gpp = np.convolve(np.convolve(faa, tay10, 'same'), tay10, 'same')
|
|
392
|
+
vccpp = np.convolve(np.convolve(vcc, tay10, 'same'), tay10, 'same')
|
|
393
|
+
vepp = np.convolve(np.convolve(ve, tay10, 'same'), tay10, 'same')
|
|
394
|
+
alpp = np.convolve(np.convolve(al, tay10, 'same'), tay10, 'same')
|
|
395
|
+
axpp = np.convolve(np.convolve(ax, tay10, 'same'), tay10, 'same')
|
|
396
|
+
levpp = np.convolve(np.convolve(level, tay10, 'same'), tay10, 'same')
|
|
397
|
+
|
|
398
|
+
# trim the ends
|
|
399
|
+
gpp = gpp[10:-10]
|
|
400
|
+
vccpp = vccpp[10:-10]
|
|
401
|
+
vepp = vepp[10:-10]
|
|
402
|
+
alpp = alpp[10:-10]
|
|
403
|
+
axpp = axpp[10:-10]
|
|
404
|
+
levpp = levpp[10:-10]
|
|
405
|
+
|
|
406
|
+
# fairly narrow filter to get rid of any high-freq noise generated by the double derivation
|
|
407
|
+
filterlength = 100 # Aliod code has this as 10...but that gives VERY different cc values
|
|
408
|
+
taps = int(2*filterlength)
|
|
409
|
+
BM = firwin(taps, 1/taps, window='blackman')
|
|
410
|
+
fgpp = lfilter(BM, 1, gpp)
|
|
411
|
+
fvccpp = lfilter(BM, 1, vccpp)
|
|
412
|
+
fvepp = lfilter(BM, 1, vepp)
|
|
413
|
+
falpp = lfilter(BM, 1, alpp)
|
|
414
|
+
faxpp = lfilter(BM, 1, axpp)
|
|
415
|
+
flevpp = lfilter(BM, 1, levpp)
|
|
416
|
+
|
|
417
|
+
# trim off filter transients
|
|
418
|
+
fgpp = fgpp[taps:-taps]
|
|
419
|
+
fvccpp = fvccpp[taps:-taps]
|
|
420
|
+
fvepp = fvepp[taps:-taps]
|
|
421
|
+
falpp = falpp[taps:-taps]
|
|
422
|
+
faxpp = faxpp[taps:-taps]
|
|
423
|
+
flevpp = flevpp[taps:-taps]
|
|
424
|
+
|
|
425
|
+
faa_tf = np.append(faa_tf, fgpp)
|
|
426
|
+
vcc_tf = np.append(vcc_tf, fvccpp)
|
|
427
|
+
ve_tf = np.append(ve_tf, fvepp)
|
|
428
|
+
al_tf = np.append(al_tf, falpp)
|
|
429
|
+
ax_tf = np.append(ax_tf, faxpp)
|
|
430
|
+
le_tf = np.append(le_tf, flevpp)
|
|
431
|
+
|
|
432
|
+
faa_tf = np.array(faa_tf).flatten()
|
|
433
|
+
vcc_tf = np.array(vcc_tf).flatten()
|
|
434
|
+
ve_tf = np.array(ve_tf).flatten()
|
|
435
|
+
al_tf = np.array(al_tf).flatten()
|
|
436
|
+
ax_tf = np.array(ax_tf).flatten()
|
|
437
|
+
le_tf = np.array(le_tf).flatten()
|
|
438
|
+
|
|
439
|
+
# solve for curvature equation by simple regression (OLS)
|
|
440
|
+
df = DataFrame({'ve': ve_tf, 'vcc': vcc_tf, 'al': al_tf,
|
|
441
|
+
'ax': ax_tf, 'lev': le_tf, 'g': -faa_tf})
|
|
442
|
+
x = df[['ve', 'vcc', 'al', 'ax', 'lev']]
|
|
443
|
+
y = df['g']
|
|
444
|
+
x = add_constant(x)
|
|
445
|
+
model = OLS(y, x).fit()
|
|
446
|
+
return df, model
|
|
447
|
+
|
|
448
|
+
########################################################################
|
|
449
|
+
# things loosely connected to tilt corrections
|
|
450
|
+
########################################################################
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def center_diff(y, n, samp):
|
|
454
|
+
""" Numerical derivatives, central difference of nth order
|
|
455
|
+
|
|
456
|
+
:param y: data to differentiate
|
|
457
|
+
:type y: array_like
|
|
458
|
+
:param n: order, either 1 or 2
|
|
459
|
+
:type n: int
|
|
460
|
+
:param samp: sampling rate
|
|
461
|
+
:type samp: float
|
|
462
|
+
|
|
463
|
+
:return: 1st or 2nd order derivative of **y**
|
|
464
|
+
"""
|
|
465
|
+
if n == 1:
|
|
466
|
+
return (y[2:] - y[:-2])*(samp/2)
|
|
467
|
+
elif n == 2:
|
|
468
|
+
return (y[:-2] - 2*y[1:-1] + y[2:])*(samp**2)
|
|
469
|
+
else:
|
|
470
|
+
print('bad order for derivative')
|
|
471
|
+
return -999
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def up_vecs(dt, g, cacc, lacc, on_off, cper, cdamp, lper, ldamp):
|
|
475
|
+
""" Calculate 3xN matrix of platform up-pointing vectors in (cross, long) coordinates
|
|
476
|
+
|
|
477
|
+
:param dt: sampling interval in seconds
|
|
478
|
+
:type dt: float
|
|
479
|
+
:param g: latitudinal correction term (2nd orfer FA plus ellipsoid)
|
|
480
|
+
:type g: array_like
|
|
481
|
+
:param cacc: cross-axis acceleration
|
|
482
|
+
:type cacc: array_like
|
|
483
|
+
:param lacc: long-axis acceleration
|
|
484
|
+
:type lacc: array_like
|
|
485
|
+
:param on_off: flag for "good" data - can be used to zero out data points when the meter
|
|
486
|
+
is clamped or otherwise not operational
|
|
487
|
+
:type on_off: array_like
|
|
488
|
+
:param cper: platform period in seconds for the cross-axis tilt filter
|
|
489
|
+
:type cper: float
|
|
490
|
+
:param cdamp: platform damping term for cross-axis tilt filter
|
|
491
|
+
:type cdamp: float
|
|
492
|
+
:param lper: platform period in seconds for the long-axis tilt filter
|
|
493
|
+
:type lper: float
|
|
494
|
+
:param ldamp: platform damping term for long-axis tilt filter
|
|
495
|
+
:type ldamp: float
|
|
496
|
+
|
|
497
|
+
:return: **up_vecs** (*3xN ndarray*) - cross, long, and up vectors for the platform
|
|
498
|
+
|
|
499
|
+
"""
|
|
500
|
+
# clean out any nans in the accelerations
|
|
501
|
+
cacc[np.isnan(cacc)] = 0
|
|
502
|
+
lacc[np.isnan(lacc)] = 0
|
|
503
|
+
|
|
504
|
+
# apply the mysterious on_off
|
|
505
|
+
cacc[on_off > 0] = 0
|
|
506
|
+
lacc[on_off > 0] = 0
|
|
507
|
+
|
|
508
|
+
# make the cross-axis tilt filter
|
|
509
|
+
cnum, cden = _tilt_filter(cper, dt, cdamp)
|
|
510
|
+
|
|
511
|
+
# calculate cross-axis driving term, do tilt filtering
|
|
512
|
+
drive = cacc/g
|
|
513
|
+
drive[np.isnan(drive)] = 0
|
|
514
|
+
ctilt = lfilter(cnum, cden, drive)
|
|
515
|
+
|
|
516
|
+
# repeat all that for the long axis
|
|
517
|
+
lnum, lden = _tilt_filter(lper, dt, ldamp)
|
|
518
|
+
drive = lacc/g
|
|
519
|
+
drive[np.isnan(drive)] = 0
|
|
520
|
+
ltilt = lfilter(lnum, lden, drive)
|
|
521
|
+
|
|
522
|
+
# combine the pieces to get the platform up-pointing vectors
|
|
523
|
+
up_vecs = _calc_up_vecs(np.arctan(ctilt), np.arctan(ltilt))
|
|
524
|
+
|
|
525
|
+
return up_vecs
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _tilt_filter(per, dt, damp=False):
|
|
529
|
+
""" Filter coefficients for L&R platform tilt computation
|
|
530
|
+
|
|
531
|
+
:param per: platform period, seconds
|
|
532
|
+
:type per: float
|
|
533
|
+
:param dt: sample increment, seconds
|
|
534
|
+
:type dt: float
|
|
535
|
+
:param damp: platform damping term (default: sqrt(2)/2)
|
|
536
|
+
:type damp: float, optional
|
|
537
|
+
|
|
538
|
+
:return: filter coefficients
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
if not damp:
|
|
542
|
+
damp = np.sqrt(2)/2
|
|
543
|
+
|
|
544
|
+
# frequencies:
|
|
545
|
+
w0 = (2*np.pi)/per
|
|
546
|
+
ws = 1/np.sqrt(6371100/9.8)
|
|
547
|
+
|
|
548
|
+
om0 = (2/dt)*np.tan(w0*dt/2)
|
|
549
|
+
omS = (2/dt)*np.tan(ws*dt/2)
|
|
550
|
+
|
|
551
|
+
# first stage substitutions
|
|
552
|
+
a = (omS**2) - (om0**2)
|
|
553
|
+
b = 2*damp*om0*(2/dt)
|
|
554
|
+
c = 4/(dt**2)
|
|
555
|
+
d = om0**2
|
|
556
|
+
|
|
557
|
+
# second stage substitutions
|
|
558
|
+
d0 = b + c + d
|
|
559
|
+
b0 = (a - b)/d0
|
|
560
|
+
b1 = 2*a/d0
|
|
561
|
+
b2 = (a + b)/d0
|
|
562
|
+
a1 = 2*(d - c)/d0
|
|
563
|
+
a2 = (c + d - b)/d0
|
|
564
|
+
|
|
565
|
+
num = np.array([b0, b1, b2])
|
|
566
|
+
den = np.array([1, a1, a2])
|
|
567
|
+
|
|
568
|
+
return num, den
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _calc_up_vecs(ctilt, ltilt):
|
|
572
|
+
""" calculate 3xN matrix of platform up-vectors in (cross, long, up) coordinates
|
|
573
|
+
|
|
574
|
+
:param ctilt: cross-axis tilt angles in radians
|
|
575
|
+
:type ctilt: array_like
|
|
576
|
+
:param ltilt: long-axis tilt angles in radians
|
|
577
|
+
:type ltilt: array_like
|
|
578
|
+
|
|
579
|
+
:return: **up_vecs** (*3xN ndarray*) - (cross, long, up) for platform
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
# get increments, assuming initial is 0
|
|
583
|
+
inc_ct = np.append(ctilt[0], np.diff(ctilt))
|
|
584
|
+
inc_lt = np.append(ltilt[0], np.diff(ltilt))
|
|
585
|
+
|
|
586
|
+
# trig functions of tilts
|
|
587
|
+
sc = np.sin(ctilt)
|
|
588
|
+
cc = np.cos(ctilt)
|
|
589
|
+
sl = np.sin(ltilt)
|
|
590
|
+
cl = np.cos(ltilt)
|
|
591
|
+
|
|
592
|
+
# set up array to hold output
|
|
593
|
+
n = len(ctilt)
|
|
594
|
+
up_vecs = np.zeros((3, n))
|
|
595
|
+
|
|
596
|
+
# do rotations
|
|
597
|
+
for i in range(n):
|
|
598
|
+
# rotation matrics
|
|
599
|
+
rotc = np.array([[cc[i], 0, -sc[i]], [0, 1, 0], [sc[i], 0, cc[i]]])
|
|
600
|
+
rotl = np.array([[1, 0, 0], [0, cl[i], -sl[i]], [0, sl[i], cl[i]]])
|
|
601
|
+
|
|
602
|
+
# alternate order of rotations for reasons that I do not understand
|
|
603
|
+
plat_up = [0, 0, 1] # start with vertical platform
|
|
604
|
+
if i % 2 == 0:
|
|
605
|
+
plat_up = np.matmul(rotc, np.matmul(rotl, plat_up))
|
|
606
|
+
else:
|
|
607
|
+
plat_up = np.matmul(rotl, np.matmul(rotc, plat_up))
|
|
608
|
+
|
|
609
|
+
up_vecs[:, i] = plat_up
|
|
610
|
+
|
|
611
|
+
return up_vecs
|
|
612
|
+
|
|
613
|
+
########################################################################
|
|
614
|
+
# MBA and RMBA functions (including thermal models)
|
|
615
|
+
########################################################################
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def grav1d_padded(xtopo, topo, zlev, rho):
|
|
619
|
+
"""Calculate the gravity anomaly due to a density contrast across topography, along a line.
|
|
620
|
+
|
|
621
|
+
The input (1D) topography is padded on both ends to reduce edge effects
|
|
622
|
+
|
|
623
|
+
This function uses the method from Parker and Blakely:
|
|
624
|
+
|
|
625
|
+
R. L. Parker (1972). The Rapid Calculation of Potential Anomalies,
|
|
626
|
+
Geophys J R astr Soc 31, 447-455, DOI: 10.1111/j.1365-246X.1973.tb06513.x
|
|
627
|
+
|
|
628
|
+
R. J. Blakely (1995). "Ch. 11: Fourier-Domain Modeling" in **Potential Theory in Gravity
|
|
629
|
+
and Magnetic Applications**, Cambridge University Press, DOI: 10.1017/CBO9780511549816
|
|
630
|
+
|
|
631
|
+
.. Original 2D function written by Mark Behn, November 6, 2003
|
|
632
|
+
Translated to Python in 1D with padding by Hannah Mark, 6 October 2017
|
|
633
|
+
|
|
634
|
+
:param xtopo: x coordinates of the surface in meters (must be equally spaced)
|
|
635
|
+
:type xtopo: array_like
|
|
636
|
+
:param topo: z coordinates of the surface in meters
|
|
637
|
+
:type ztopo: array_like
|
|
638
|
+
:param zlev: vertical distance for upwards continuation in meters.
|
|
639
|
+
:type zlev: float
|
|
640
|
+
:param rho: density contrast across the topography in kg/m^3.
|
|
641
|
+
:type rho: float
|
|
642
|
+
|
|
643
|
+
:return: **anom** (*ndarray*) - gravity anomaly in mgal
|
|
644
|
+
"""
|
|
645
|
+
G = 6.673*1e-8
|
|
646
|
+
grav = 2*np.pi*G*rho
|
|
647
|
+
baselev = np.mean(topo) # mean topography for baseline
|
|
648
|
+
|
|
649
|
+
# spacing of coordinates, must be constant
|
|
650
|
+
dx = (xtopo[-1]-xtopo[0])/(len(xtopo)-1)
|
|
651
|
+
nx = len(xtopo)
|
|
652
|
+
|
|
653
|
+
wing = np.ones(2*len(topo)) # *** extra padding
|
|
654
|
+
|
|
655
|
+
# extend or mirror the profile
|
|
656
|
+
padtopo = np.append(topo[0]*wing, np.append(topo, topo[-1]*wing))
|
|
657
|
+
# padtopo = np.append(baselev*wing,np.append(topo,baselev*wing))
|
|
658
|
+
# padtopo = np.append(topo,topo[::-1])
|
|
659
|
+
nxt = len(padtopo)
|
|
660
|
+
mfx = (nxt/2.) + 1
|
|
661
|
+
|
|
662
|
+
k = (2*np.pi)/(nxt*dx) # calculate wavenumbers
|
|
663
|
+
k2 = k**2
|
|
664
|
+
xi1 = np.arange(1, mfx+1)
|
|
665
|
+
xi = np.append(xi1, xi1[-2:0:-1])
|
|
666
|
+
xxk = (xi-1)*(xi-1)*k2
|
|
667
|
+
kwn1 = np.sqrt(xxk[:nxt])
|
|
668
|
+
|
|
669
|
+
Ftopo = np.fft.fft(padtopo) # Fourier transform the topography
|
|
670
|
+
Ftopo[0] = 0
|
|
671
|
+
|
|
672
|
+
npower = 5
|
|
673
|
+
SUMtopo = copy(Ftopo)
|
|
674
|
+
for ip in range(2, npower+1): # summation per eq. 11.41 in Blakely
|
|
675
|
+
Ftopo = np.fft.fft(padtopo**ip)
|
|
676
|
+
Ftopo = Ftopo*(kwn1**(ip-1))/factorial(ip)
|
|
677
|
+
|
|
678
|
+
SUMtopo = SUMtopo + Ftopo
|
|
679
|
+
|
|
680
|
+
data = SUMtopo*grav*np.exp(-(zlev-baselev)*kwn1) # upward continuation
|
|
681
|
+
|
|
682
|
+
data = np.fft.ifft(data) # inverse Fourier transform
|
|
683
|
+
anom = np.real(data[2*nx:3*nx])*100 # factor of 100 for mgal output
|
|
684
|
+
|
|
685
|
+
return anom
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def grav2d_folding(X, Y, Z, dx, dy, drho=0.6, dz=6000, ifold=True, npower=5):
|
|
689
|
+
"""
|
|
690
|
+
Parker [1972] method for calculating gravity from 2D topographic surface with a density contrast.
|
|
691
|
+
|
|
692
|
+
The `ifold` option enables folding the input topography grid in x and y to mitigate edge effects
|
|
693
|
+
|
|
694
|
+
.. Modified from parker.m by Mark Behn, which was in turn modified from
|
|
695
|
+
parker.f by Ban-Yuan Kuo and Jian Lin.
|
|
696
|
+
|
|
697
|
+
:param X: vector of N X coordinates
|
|
698
|
+
:type X: ndarray
|
|
699
|
+
:param Y: vector of M Y coordinates
|
|
700
|
+
:type Y: ndarray
|
|
701
|
+
:param Z: matrix of Z coordinates
|
|
702
|
+
:type Z: ndarray, NxM
|
|
703
|
+
:param dx: x grid spacing, for wavenumbers [km]
|
|
704
|
+
:type dx: float
|
|
705
|
+
:param dy: y grid spacing, for wavenumbers [km]
|
|
706
|
+
:type dy: float
|
|
707
|
+
:param drho: density contrast across surface. Ex: 1.7 for water to crust, 0.6 for crust to mantle
|
|
708
|
+
:type drho: float
|
|
709
|
+
:param dz: offset depth for layer interface, added to baselevel for upward continuation [m]
|
|
710
|
+
:type dz: float
|
|
711
|
+
:param ifold: switch for folding
|
|
712
|
+
:type ifold: bool
|
|
713
|
+
:param npower: power of Taylor series expansion (default: 5)
|
|
714
|
+
:type npower: int
|
|
715
|
+
|
|
716
|
+
:return: (*ndarray*) gravity anomaly in mgal
|
|
717
|
+
"""
|
|
718
|
+
nx = len(X) # size of the grid
|
|
719
|
+
ny = len(Y)
|
|
720
|
+
|
|
721
|
+
zmax = max(Z.flatten()) # get min and max for scaling
|
|
722
|
+
zmin = min(Z.flatten())
|
|
723
|
+
if zmax < -1e10:
|
|
724
|
+
zmax = -1e10
|
|
725
|
+
if zmin > 1e10:
|
|
726
|
+
zmin = 1e10
|
|
727
|
+
|
|
728
|
+
slev = np.mean(Z.flatten())
|
|
729
|
+
|
|
730
|
+
Z = 100*(slev-Z)
|
|
731
|
+
slev = slev*100
|
|
732
|
+
dx = 100000*dx
|
|
733
|
+
dy = 100000*dy
|
|
734
|
+
dz = dz*100
|
|
735
|
+
|
|
736
|
+
# to fold or not to fold--
|
|
737
|
+
if ifold:
|
|
738
|
+
nxt = nx*2
|
|
739
|
+
nyt = ny*2
|
|
740
|
+
else:
|
|
741
|
+
nxt = nx
|
|
742
|
+
nyt = ny
|
|
743
|
+
|
|
744
|
+
G = 6.673e-8 # gravitational constant
|
|
745
|
+
conv = 1000 # gals to mgals
|
|
746
|
+
grav1 = 2*np.pi*G*conv
|
|
747
|
+
|
|
748
|
+
grav = grav1*drho
|
|
749
|
+
|
|
750
|
+
# for wavenumbers
|
|
751
|
+
kint1 = (2*np.pi)/(nxt*dx)
|
|
752
|
+
kint2 = (2*np.pi)/(nyt*dy)
|
|
753
|
+
kx2 = kint1*kint1
|
|
754
|
+
ky2 = kint2*kint2
|
|
755
|
+
|
|
756
|
+
# folding frequency:
|
|
757
|
+
mfx = int(nxt/2 + 1)
|
|
758
|
+
mfy = int(nyt/2 + 1)
|
|
759
|
+
mfx2 = mfx*2
|
|
760
|
+
mfy2 = mfy*2
|
|
761
|
+
|
|
762
|
+
# the important part:
|
|
763
|
+
# (1) compute wavenumbers
|
|
764
|
+
# (2) transform topography with fft
|
|
765
|
+
# (3) sum over powers with those wavenumbers
|
|
766
|
+
# (4) upward continue, transform back to space domain, multiply
|
|
767
|
+
# by constants
|
|
768
|
+
|
|
769
|
+
# wavenumbers:
|
|
770
|
+
yj1 = np.arange(1, mfy+1)
|
|
771
|
+
yj = np.append(yj1, yj1[mfy-2:0:-1])
|
|
772
|
+
yyk = (yj-1)*(yj-1)*ky2
|
|
773
|
+
|
|
774
|
+
xi1 = np.arange(1, mfx+1)
|
|
775
|
+
xi = np.append(xi1, xi1[mfx-2:0:-1])
|
|
776
|
+
xxk = (xi-1)*(xi-1)*kx2
|
|
777
|
+
|
|
778
|
+
kwn1 = np.zeros((nxt, nyt))
|
|
779
|
+
for j in range(nyt):
|
|
780
|
+
kwn1[:, j] = np.sqrt(xxk + yyk[j])
|
|
781
|
+
|
|
782
|
+
kwn1 = kwn1.T
|
|
783
|
+
|
|
784
|
+
# fold the data
|
|
785
|
+
if ifold == 1:
|
|
786
|
+
Z = np.vstack((Z, np.flipud(Z))) # fold in X
|
|
787
|
+
Z = np.hstack((Z, np.fliplr(Z))) # fold in Y
|
|
788
|
+
|
|
789
|
+
# first power
|
|
790
|
+
data = np.copy(Z)
|
|
791
|
+
data = np.fft.fft2(data)
|
|
792
|
+
data[0, 0] = 0
|
|
793
|
+
|
|
794
|
+
# sum over the other powers
|
|
795
|
+
csum = np.copy(data) # for adding summation terms
|
|
796
|
+
fact = 1
|
|
797
|
+
for i in range(2, npower+1):
|
|
798
|
+
fact = fact*i
|
|
799
|
+
data = np.copy(Z)**i
|
|
800
|
+
data = np.fft.fft2(data)
|
|
801
|
+
|
|
802
|
+
data = data*(kwn1**(i-1))/fact
|
|
803
|
+
k, l = np.where(kwn1 == 0)
|
|
804
|
+
data[k, l] = 0
|
|
805
|
+
|
|
806
|
+
csum = csum + np.copy(data)
|
|
807
|
+
|
|
808
|
+
# upward continuation
|
|
809
|
+
zlev = slev+dz
|
|
810
|
+
data = csum*grav*np.exp(-zlev*kwn1)
|
|
811
|
+
|
|
812
|
+
data = np.fft.ifft2(data)
|
|
813
|
+
sdata = np.real(data[:ny, :nx]) # back in the spatial domain
|
|
814
|
+
|
|
815
|
+
return sdata
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def grav2d_layer_variable_density(rho, dx, dy, z1, z2):
|
|
819
|
+
"""
|
|
820
|
+
Calculate the gravity contribution from a layer of equal thickness with
|
|
821
|
+
an inhomogenous density distribution in x and y (homogeneous in z)
|
|
822
|
+
|
|
823
|
+
.. Based on glayer.m by Mark Behn
|
|
824
|
+
|
|
825
|
+
:param rho: 2D density distribution [kg/m^3]
|
|
826
|
+
:type rho: ndarray
|
|
827
|
+
:param dx,dy: sample intervals in km
|
|
828
|
+
:type dx,dy: float
|
|
829
|
+
:param z1,z2: depth to top and bottom of layer in km (both >0)
|
|
830
|
+
:type z1,z2: float
|
|
831
|
+
|
|
832
|
+
:return: (*ndarray*) gravity in mgal
|
|
833
|
+
"""
|
|
834
|
+
|
|
835
|
+
si2mg = 1e5
|
|
836
|
+
km2m = 1e3
|
|
837
|
+
G = 6.673e-11
|
|
838
|
+
grav = 2*np.pi*G
|
|
839
|
+
|
|
840
|
+
ny, nx = rho.shape
|
|
841
|
+
dkx = 2*np.pi/(nx*dx)
|
|
842
|
+
dky = 2*np.pi/(ny*dy)
|
|
843
|
+
|
|
844
|
+
ifrho = np.fft.fft2(rho) # take 2D fft of densities
|
|
845
|
+
|
|
846
|
+
crho = np.empty((ny, nx), dtype=complex)
|
|
847
|
+
for j in range(nx):
|
|
848
|
+
for i in range(ny):
|
|
849
|
+
kx, ky = _kvalue(i, j, nx, ny, dkx, dky)
|
|
850
|
+
k = np.sqrt(kx**2 + ky**2)
|
|
851
|
+
if k == 0:
|
|
852
|
+
crho[i, j] = 0
|
|
853
|
+
else:
|
|
854
|
+
crho[i, j] = ifrho[i, j]*grav*(np.exp(-k*z1)-np.exp(-k*z2))/k
|
|
855
|
+
|
|
856
|
+
grho = np.fft.ifft2(crho)
|
|
857
|
+
grho = np.real(grho)*si2mg*km2m
|
|
858
|
+
|
|
859
|
+
return grho
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def _kvalue(i, j, nx, ny, dkx, dky):
|
|
863
|
+
"""
|
|
864
|
+
Get wavenumber coordinates of one element of a rectangular grid
|
|
865
|
+
|
|
866
|
+
:param i,j: indices in ky,kx directions
|
|
867
|
+
:type i,j: int
|
|
868
|
+
:param nx,ny: dimensions of the grid in the ky,kx directions
|
|
869
|
+
:type nx,ny: int
|
|
870
|
+
:param dkx,dky: sample intervals in kx,ky directions
|
|
871
|
+
:type dkx,dky: float
|
|
872
|
+
|
|
873
|
+
:return: **kx, ky** (*float*) - x and y wavenumbers at (i, j)
|
|
874
|
+
"""
|
|
875
|
+
|
|
876
|
+
nyqx = nx/2+1
|
|
877
|
+
nyqy = ny/2+1
|
|
878
|
+
|
|
879
|
+
if j <= nyqx:
|
|
880
|
+
kx = (j)*dkx
|
|
881
|
+
else:
|
|
882
|
+
kx = (j-nx)*dkx
|
|
883
|
+
|
|
884
|
+
if i <= nyqy:
|
|
885
|
+
ky = (i)*dky
|
|
886
|
+
else:
|
|
887
|
+
ky = (i-ny)*dky
|
|
888
|
+
return kx, ky
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def therm_halfspace(x, z, u=0.01, Tm=1350, time=False, rhom=3300, rhow=1000,
|
|
892
|
+
a=3.e-5, k=1.e-6):
|
|
893
|
+
"""Calculate thermal structure for a half space cooling model.
|
|
894
|
+
|
|
895
|
+
Reference:
|
|
896
|
+
|
|
897
|
+
D. Turcotte & G. Schubert (2014). Geodynamics. Cambridge
|
|
898
|
+
University Press. DOI: 10.1017/CBO9780511843877
|
|
899
|
+
Relevant pages: 161-162, 174-176 in 2nd or 3rd ed?
|
|
900
|
+
|
|
901
|
+
.. Written by Mark D. Behn, November 20, 2003.
|
|
902
|
+
Translated to Python + modded for plate age by Hannah Mark, October 2017
|
|
903
|
+
|
|
904
|
+
:param x: vector of across-axis distance (meters) OR of plate ages (Myr)
|
|
905
|
+
:type x: array_like
|
|
906
|
+
:param z: vector of depth (meters)
|
|
907
|
+
:type z: array_like
|
|
908
|
+
:param u: spreading rate (m/yr)
|
|
909
|
+
:type u: float
|
|
910
|
+
:param time: switch for x vs age input: if ages, set time=True
|
|
911
|
+
and u will be ignored.
|
|
912
|
+
:type time: bool
|
|
913
|
+
:param rhom: mantle density, kg/m^3, default 3300
|
|
914
|
+
:type rhom: float, optional
|
|
915
|
+
:param rhow: water density, kg/m^3, default 1000
|
|
916
|
+
:type rhow: float, optional
|
|
917
|
+
:param a: coefficient of thermal expansion, m^2/sec, default 3e-5
|
|
918
|
+
:type a: float, optional
|
|
919
|
+
:param k: thermal diffusivity, m^2/sec, default 1e-6
|
|
920
|
+
:type k: float, optional
|
|
921
|
+
|
|
922
|
+
:return:
|
|
923
|
+
- **T** (*ndarray*) - gridded temperature over (x, z)
|
|
924
|
+
- **W** (*ndarray*) - seafloor subsidence in meters
|
|
925
|
+
"""
|
|
926
|
+
|
|
927
|
+
To = 0 # surface temperature [K]
|
|
928
|
+
# a = 6.e-5 # coeff of thermal expansion [m^2/sec] # used for SCARF calcs (???)
|
|
929
|
+
# k = 2.e-6 # thermal diffusivity [m^2/sec]
|
|
930
|
+
|
|
931
|
+
secyr = 365.25*24*3600 # seconds per year
|
|
932
|
+
|
|
933
|
+
if time:
|
|
934
|
+
x = x*1e6*secyr # convert Myr to sec
|
|
935
|
+
elif not time:
|
|
936
|
+
x = abs(x)/(u/secyr) # convert m to sec
|
|
937
|
+
|
|
938
|
+
X, Z = np.meshgrid(x, z) # grid up x,z pairs
|
|
939
|
+
|
|
940
|
+
# mantle temperature
|
|
941
|
+
T = To + (Tm-To) * erf(Z/(2*(k*X)**.5))
|
|
942
|
+
|
|
943
|
+
# seafloor subsidence
|
|
944
|
+
W = ((2*rhom*a*(Tm-To))/(rhom-rhow)) * \
|
|
945
|
+
(((k*X[0, :])/(np.pi))**.5)
|
|
946
|
+
|
|
947
|
+
return T, W
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def therm_Z_halfspace(x, T, u=0.01, Tm=1350, time=False, rhom=3300, rhow=1000,
|
|
951
|
+
a=3.e-5, k=1.e-6):
|
|
952
|
+
"""Calculate depth to an isotherm for a half-space cooling model.
|
|
953
|
+
|
|
954
|
+
:param x: vector of across-axis distance [m] OR plate age [Myr]
|
|
955
|
+
:type x: array_like
|
|
956
|
+
:param T: isotherm of choice [K]
|
|
957
|
+
:type T: float
|
|
958
|
+
:param u: spreading rate [m/yr], default 0.01
|
|
959
|
+
:type u: float
|
|
960
|
+
:param time: switch for x vs age input - if time=True,
|
|
961
|
+
u is ignored, default False
|
|
962
|
+
:type time: bool
|
|
963
|
+
:param Tm: mantle potential temperature [K], default 1350
|
|
964
|
+
:type Tm: float, optional
|
|
965
|
+
:param rhom: mantle density, kg/m^3, default 3300
|
|
966
|
+
:type rhom: float, optional
|
|
967
|
+
:param rhow: water density, kg/m^3, default 1000
|
|
968
|
+
:type rhow: float, optional
|
|
969
|
+
:param a: coefficient of thermal expansion, m^2/sec, default 3e-5
|
|
970
|
+
:type a: float, optional
|
|
971
|
+
:param k: thermal diffusivity, m^2/sec, default 1e-6
|
|
972
|
+
:type k: float, optional
|
|
973
|
+
|
|
974
|
+
:returns:
|
|
975
|
+
- **Z** (*ndarray*) - depth of this isotherm below the seafloor in meters
|
|
976
|
+
- **W** (*ndarray*) - seafloor subsidence in meters
|
|
977
|
+
"""
|
|
978
|
+
|
|
979
|
+
To = 0 # surface temperature [K]
|
|
980
|
+
|
|
981
|
+
secyr = 365.25*24*3600 # seconds per year
|
|
982
|
+
|
|
983
|
+
if time:
|
|
984
|
+
x = x*1e6*secyr # convert Myr to sec
|
|
985
|
+
elif not time:
|
|
986
|
+
x = abs(x)/(u/secyr) # convert m to sec
|
|
987
|
+
|
|
988
|
+
Z = 2*np.sqrt((k*x))*erfcinv((T-Tm)/(To-Tm))
|
|
989
|
+
|
|
990
|
+
W = ((2*rhom*a*(Tm-To))/(rhom-rhow)) * \
|
|
991
|
+
(((k*x)/(np.pi))**.5)
|
|
992
|
+
|
|
993
|
+
return Z, W
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def therm_plate(x, z, u=0.01, zL0=100.e3, Tm=1350, time=False, rhom=3300, rhow=1000,
|
|
997
|
+
a=3.e-5, k=1.e-6):
|
|
998
|
+
"""Calculate thermal structure for the plate cooling model.
|
|
999
|
+
|
|
1000
|
+
Reference:
|
|
1001
|
+
|
|
1002
|
+
D. Turcotte & G. Schubert (2014). Geodynamics. Cambridge
|
|
1003
|
+
University Press. DOI: 10.1017/CBO9780511843877
|
|
1004
|
+
|
|
1005
|
+
.. Written by Mark D. Behn, November 20, 2003.
|
|
1006
|
+
Translated to Python + modded for plate age by Hannah Mark, October 2017
|
|
1007
|
+
|
|
1008
|
+
:param x: vector of across-axis distance (meters) OR plate age (Myr)
|
|
1009
|
+
:type x: array_like
|
|
1010
|
+
:param z: vector of depth (meters)
|
|
1011
|
+
:type z: array_like
|
|
1012
|
+
:param u: spreading rate (m/yr), default 0.01
|
|
1013
|
+
:type u: float
|
|
1014
|
+
:param zL0: plate thickness (meters), default 100e3
|
|
1015
|
+
:type zL0: float
|
|
1016
|
+
:param time: switch for x vs age input. If time=True, u is ignored.
|
|
1017
|
+
:type time: bool
|
|
1018
|
+
:param Tm: mantle potential temperature (K), default 1350
|
|
1019
|
+
:type Tm: float, optional
|
|
1020
|
+
:param rhom: mantle density, kg/m^3, default 3300
|
|
1021
|
+
:type rhom: float, optional
|
|
1022
|
+
:param rhow: water density, kg/m^3, default 1000
|
|
1023
|
+
:type rhow: float, optional
|
|
1024
|
+
:param a: coefficient of thermal expansion, m^2/sec, default 3e-5
|
|
1025
|
+
:type a: float, optional
|
|
1026
|
+
:param k: thermal diffusivity, m^2/sec, default 1e-6
|
|
1027
|
+
:type k: float, optional
|
|
1028
|
+
|
|
1029
|
+
:returns:
|
|
1030
|
+
- **T** (*ndarray*) - gridded temperature over (x, z)
|
|
1031
|
+
- **W** (*ndarray*) - seafloor subsidence in meters
|
|
1032
|
+
"""
|
|
1033
|
+
|
|
1034
|
+
To = 0 # surface temperature [K]
|
|
1035
|
+
|
|
1036
|
+
secyr = 365.25*24*3600 # seconds per year
|
|
1037
|
+
|
|
1038
|
+
X, Z = np.meshgrid(x, z)
|
|
1039
|
+
|
|
1040
|
+
if time:
|
|
1041
|
+
t = X*1e6*secyr # convert Myr to sec
|
|
1042
|
+
elif not time:
|
|
1043
|
+
t = abs(X)/(u/secyr) # convert across-axis distance to time in SECONDS
|
|
1044
|
+
|
|
1045
|
+
Tterm2 = (2/np.pi)*np.exp(-k*(np.pi**2)*t/(zL0**2))*np.sin(np.pi*Z/zL0)
|
|
1046
|
+
Tterm3 = (1/np.pi)*np.exp(-4*k*(np.pi**2)*t/(zL0**2))*np.sin(2*np.pi*Z/zL0)
|
|
1047
|
+
Tterm4 = (2/np.pi/3)*np.exp(-9*k*(np.pi**2)
|
|
1048
|
+
* t/(zL0**2))*np.sin(3*np.pi*Z/zL0)
|
|
1049
|
+
Tterm5 = (2/np.pi/4)*np.exp(-16*k*(np.pi**2)
|
|
1050
|
+
* t/(zL0**2))*np.sin(4*np.pi*Z/zL0)
|
|
1051
|
+
Tterm6 = (2/np.pi/5)*np.exp(-25*k*(np.pi**2)
|
|
1052
|
+
* t/(zL0**2))*np.sin(5*np.pi*Z/zL0)
|
|
1053
|
+
Tterm7 = (2/np.pi/6)*np.exp(-36*k*(np.pi**2)
|
|
1054
|
+
* t/(zL0**2))*np.sin(6*np.pi*Z/zL0)
|
|
1055
|
+
Tterm8 = (2/np.pi/7)*np.exp(-49*k*(np.pi**2)
|
|
1056
|
+
* t/(zL0**2))*np.sin(7*np.pi*Z/zL0)
|
|
1057
|
+
Tterm9 = (2/np.pi/8)*np.exp(-64*k*(np.pi**2)
|
|
1058
|
+
* t/(zL0**2))*np.sin(8*np.pi*Z/zL0)
|
|
1059
|
+
Tterm10 = (2/np.pi/9)*np.exp(-81*k*(np.pi**2)
|
|
1060
|
+
* t/(zL0**2))*np.sin(9*np.pi*Z/zL0)
|
|
1061
|
+
Tterm11 = (2/np.pi/10)*np.exp(-100*k*(np.pi**2)
|
|
1062
|
+
* t/(zL0**2))*np.sin(10*np.pi*Z/zL0)
|
|
1063
|
+
|
|
1064
|
+
T = To + (Tm - To)*(Z/zL0 + Tterm2 + Tterm3 + Tterm4 + Tterm5 + Tterm6 + Tterm7 +
|
|
1065
|
+
Tterm8 + Tterm9 + Tterm10 + Tterm11)
|
|
1066
|
+
|
|
1067
|
+
mantle = np.where(Z > zL0)[0]
|
|
1068
|
+
T[mantle] = Tm
|
|
1069
|
+
|
|
1070
|
+
Wterm2 = (4/np.pi**2)*np.exp(-k*(np.pi**2)*t[0, :]/(zL0**2))
|
|
1071
|
+
Wterm3 = (4/(9*np.pi**2))*np.exp(-k*9*(np.pi**2)*t[0, :]/(zL0**2))
|
|
1072
|
+
Wterm4 = (4/(25*np.pi**2))*np.exp(-k*25*(np.pi**2)*t[0, :]/(zL0**2))
|
|
1073
|
+
|
|
1074
|
+
W = ((rhom*a*(Tm-To)*zL0)/(rhom-rhow)) * (1./2 - Wterm2 - Wterm3 - Wterm4)
|
|
1075
|
+
|
|
1076
|
+
return T, W
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def therm_Z_plate(x, T, u=0.01, zL0=100.e3, Tm=1350, time=False,
|
|
1080
|
+
minz=0, maxz=100e3, zsp=1e2, rhom=3300, rhow=1000,
|
|
1081
|
+
a=3.e-5, k=1.e-6):
|
|
1082
|
+
"""Calculate approximate depth to an isotherm in the plate cooling model
|
|
1083
|
+
|
|
1084
|
+
This is done by calculating a temperature field with a decent z spacing
|
|
1085
|
+
and finding the closest points to the isotherm, so it depends strongly
|
|
1086
|
+
on the z spacing that you use. If you need depth to multiple isotherms
|
|
1087
|
+
it's most efficient to get them all at once (using a longer array for
|
|
1088
|
+
T) so the whole temperature field is only calculated one time.
|
|
1089
|
+
|
|
1090
|
+
:param x: array of across-axis distance (meters) OR plate age (Myr)
|
|
1091
|
+
:type x: array_like
|
|
1092
|
+
:param T: temperatures for which you want isotherms (K)
|
|
1093
|
+
:type T: array_like
|
|
1094
|
+
:param u: spreading rate (m/yr), default 0.01
|
|
1095
|
+
:type u: float
|
|
1096
|
+
:param zL0: plate thickness (meters), default 100e3
|
|
1097
|
+
:type zL0: float
|
|
1098
|
+
:param Tm: mantle potential temperature (K), default 1350
|
|
1099
|
+
:type Tm: float, optional
|
|
1100
|
+
:param time: switch for x vs age input. If time=True, u is ignored.
|
|
1101
|
+
:type time: bool
|
|
1102
|
+
:param minz: minimum z for calculating T field (meters), default 0
|
|
1103
|
+
:type minz: float
|
|
1104
|
+
:param maxz: maximum z (meters), default 100e3
|
|
1105
|
+
:type maxz: float
|
|
1106
|
+
:param zps: z spacing for grid (meters), default 1e3
|
|
1107
|
+
:type zps: float
|
|
1108
|
+
:param rhom: mantle density, kg/m^3, default 3300
|
|
1109
|
+
:type rhom: float, optional
|
|
1110
|
+
:param rhow: water density, kg/m^3, default 1000
|
|
1111
|
+
:type rhow: float, optional
|
|
1112
|
+
:param a: coefficient of thermal expansion, m^2/sec, default 3e-5
|
|
1113
|
+
:type a: float, optional
|
|
1114
|
+
:param k: thermal diffusivity, m^2/sec, default 1e-6
|
|
1115
|
+
:type k: float, optional
|
|
1116
|
+
|
|
1117
|
+
:returns: **ziso** (*ndarray*) - depths to isotherms, z(T,x)
|
|
1118
|
+
"""
|
|
1119
|
+
|
|
1120
|
+
z = np.arange(minz, maxz, zsp)
|
|
1121
|
+
|
|
1122
|
+
Tp, _ = therm_plate(x, z, u=u, zL0=zL0, Tm=Tm, time=time, rhom=rhom, rhow=rhow,
|
|
1123
|
+
a=a, k=k) # calculate the whole temperature field
|
|
1124
|
+
|
|
1125
|
+
ziso = np.zeros((len(T), len(x)))
|
|
1126
|
+
for i in range(len(T)): # loop isotherms, find depths
|
|
1127
|
+
for j in range(len(x)):
|
|
1128
|
+
ziso[i, j] = z[np.argmin(abs(Tp[:, j]-T[i]))]
|
|
1129
|
+
|
|
1130
|
+
return ziso
|
|
1131
|
+
|
|
1132
|
+
########################################################################
|
|
1133
|
+
# crustal thickness functions
|
|
1134
|
+
########################################################################
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def crustal_thickness_2D(ur, nx=1000, ny=1, dx=1.3, dy=0, zdown=10, rho=0.4,
|
|
1138
|
+
wlarge=45, wsmall=25, back=False):
|
|
1139
|
+
"""
|
|
1140
|
+
Downward continuation of gravity to "topographic relief" ie crustal thickness
|
|
1141
|
+
|
|
1142
|
+
This can be used in 2D, but also works for a single line
|
|
1143
|
+
given ny=1 (which is the default)
|
|
1144
|
+
|
|
1145
|
+
.. Written by Hannah Mark (MIT/WHOI), October 2017
|
|
1146
|
+
Modeled on down_2d.f by Jian Lin (WHOI)
|
|
1147
|
+
|
|
1148
|
+
:param ur: residual gravity anomaly, mgal
|
|
1149
|
+
:type ur: array_like
|
|
1150
|
+
:param nx: number of points in x direction, default 1000
|
|
1151
|
+
:type nx: int
|
|
1152
|
+
:param ny: number of points in y direction, default 1 (>1 for 2D)
|
|
1153
|
+
:type ny: int
|
|
1154
|
+
:param dx: spacing between x points, km, default 1.3
|
|
1155
|
+
:type dx: float
|
|
1156
|
+
:param dy: spacing between y points, km, default 0 (>0 for 2D)
|
|
1157
|
+
:type dy: float
|
|
1158
|
+
:param zdown: downward continuation depth, km, default 10
|
|
1159
|
+
:type zdown: float
|
|
1160
|
+
:param rho: density difference crust to mantle, g/cm^3, default 0.4
|
|
1161
|
+
:type rho: float
|
|
1162
|
+
:param wlarge: max wavelength for taper/cutoff, km, default 45
|
|
1163
|
+
:type wlarge: float
|
|
1164
|
+
:param wsmall: min wavelength for taper/cutoff, km, default 25
|
|
1165
|
+
:type wsmall: float
|
|
1166
|
+
:param back: switch for doing reverse tranform
|
|
1167
|
+
:type back: bool
|
|
1168
|
+
|
|
1169
|
+
:returns:
|
|
1170
|
+
- **crustal thickness** (*ndarray*) - thickness variation in km
|
|
1171
|
+
- **recovered gravity** (*ndarray*) - back-calculated RMBA, (optional, if back=True)
|
|
1172
|
+
"""
|
|
1173
|
+
assert wlarge > wsmall, 'wlarge must be larger than wsmall'
|
|
1174
|
+
|
|
1175
|
+
zmin = min(ur)
|
|
1176
|
+
zmax = max(ur)
|
|
1177
|
+
ave = np.mean(ur)
|
|
1178
|
+
|
|
1179
|
+
# shift to a new reference and convert milligal to gal
|
|
1180
|
+
# sign is switched so that positive residual = crustal thinning
|
|
1181
|
+
ur = -0.001*(ur - ave)
|
|
1182
|
+
|
|
1183
|
+
# convert km to cm for spatial params
|
|
1184
|
+
dx = dx*1e5
|
|
1185
|
+
dy = dy*1e5
|
|
1186
|
+
zdown = zdown*1e5
|
|
1187
|
+
|
|
1188
|
+
# calculate wavenumbers for taper/cutoff
|
|
1189
|
+
# we will taper btwn kcut1 and kcut2; cutoff >kcut2
|
|
1190
|
+
wlarge = wlarge*1e5 # convert km to cm
|
|
1191
|
+
wsmall = wsmall*1e5
|
|
1192
|
+
kcut1 = 2*np.pi/wlarge
|
|
1193
|
+
kcut2 = 2*np.pi/wsmall
|
|
1194
|
+
dkcut = kcut2 - kcut1
|
|
1195
|
+
mfx = nx/2 + 1
|
|
1196
|
+
mfy = ny/2 + 1
|
|
1197
|
+
|
|
1198
|
+
prod = 1./(nx*ny) # dimension correction factor
|
|
1199
|
+
|
|
1200
|
+
G = 6.673e-8 # cgs gravity
|
|
1201
|
+
topo1 = 1/(2*np.pi*G*rho) # gravity-to-topography transfer
|
|
1202
|
+
|
|
1203
|
+
kint1 = 2*np.pi/(nx*dx)
|
|
1204
|
+
if dy != 0:
|
|
1205
|
+
kint2 = 2*np.pi/(ny*dy)
|
|
1206
|
+
else:
|
|
1207
|
+
kint2 = 0
|
|
1208
|
+
|
|
1209
|
+
kwn1 = np.zeros((nx, ny)) # compute wavenumbers in 2D
|
|
1210
|
+
for j in range(ny):
|
|
1211
|
+
yj = j
|
|
1212
|
+
if j > mfy:
|
|
1213
|
+
yj = mfy*2 - j
|
|
1214
|
+
yyk = kint2**2 * (yj-1)**2
|
|
1215
|
+
|
|
1216
|
+
for i in range(nx):
|
|
1217
|
+
xi = i
|
|
1218
|
+
if i > mfx:
|
|
1219
|
+
xi = mfx*2 - i
|
|
1220
|
+
xxk = kint1**2 * (xi-1)**2
|
|
1221
|
+
|
|
1222
|
+
kwn1[i, j] = np.sqrt(xxk + yyk)
|
|
1223
|
+
|
|
1224
|
+
# Fourier transform of the gravity residual
|
|
1225
|
+
# this was passed as a 1D array even for a 2D problem
|
|
1226
|
+
ur_arr = ur.reshape(nx, ny)
|
|
1227
|
+
ur_arr_ft = np.fft.fft2(ur_arr) # 2D fft
|
|
1228
|
+
|
|
1229
|
+
# apply wavenumbers, taper
|
|
1230
|
+
for j in range(ny):
|
|
1231
|
+
for i in range(nx):
|
|
1232
|
+
wgt = 1
|
|
1233
|
+
if kwn1[i, j] > kcut2:
|
|
1234
|
+
wgt = 0
|
|
1235
|
+
elif kwn1[i, j] > kcut1 and kwn1[i, j] <= kcut2:
|
|
1236
|
+
t = (kwn1[i, j]-kcut1)/dkcut*np.pi
|
|
1237
|
+
wgt = (np.cos(t)+1)/2
|
|
1238
|
+
|
|
1239
|
+
ur_arr_ft[i, j] = ur_arr_ft[i, j] * \
|
|
1240
|
+
np.exp(kwn1[i, j]*zdown)*topo1*wgt
|
|
1241
|
+
|
|
1242
|
+
# inverse Fourier transform
|
|
1243
|
+
ur_arr_2 = np.fft.fft2(ur_arr_ft)
|
|
1244
|
+
|
|
1245
|
+
# back to vector, correct for dimensions, convert to km
|
|
1246
|
+
ur_arr_2 = ur_arr_2.reshape(-1, 1)
|
|
1247
|
+
ur_arr_2 = ur_arr_2*prod/1e5
|
|
1248
|
+
|
|
1249
|
+
if not back:
|
|
1250
|
+
return ur_arr_2
|
|
1251
|
+
elif back: # reverse the transform and upward continute to check gravity recovery
|
|
1252
|
+
for j in range(ny):
|
|
1253
|
+
for i in range(nx):
|
|
1254
|
+
ur_arr_ft[i, j] = -ur_arr_ft[i, j] * \
|
|
1255
|
+
np.exp(-kwn1[i, j]*zdown)/topo1
|
|
1256
|
+
ur_back = np.fft.fft2(ur_arr_ft)
|
|
1257
|
+
ur_back = ur_back.reshape(-1, 1)
|
|
1258
|
+
ur_back = ur_back*prod*1000+ave
|
|
1259
|
+
|
|
1260
|
+
return ur_arr_2[::-1], ur_back[::-1]
|