forked from NoisyLeon/SES3DPy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
input_generator.py
347 lines (332 loc) · 17.2 KB
/
input_generator.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
338
339
340
341
342
343
344
345
346
347
# -*- coding: utf-8 -*-
"""
A python module to generate input files for ses3d
:Copyright:
Author: Lili Feng
Graduate Research Assistant
CIEI, Department of Physics, University of Colorado Boulder
email: lili.feng@colorado.edu
"""
import stations
import events
from obspy.core.util.attribdict import AttribDict
import obspy.geodetics.base
import obspy
import os
import numpy as np
from lasif import rotations
import matplotlib.pyplot as plt
import warnings
class InputFileGenerator(object):
"""An object to generate input file for SES3D
"""
def __init__(self):
self.config = AttribDict()
self.events = events.ses3dCatalog()
self.stalst = stations.StaLst()
# self.vmodel
return
def add_explosion(self, longitude, latitude, depth, m0):
"""Add explosion to catalog
"""
self.events.add_explosion(longitude=longitude, latitude=latitude, depth=depth, m0=m0)
self._check_time_step(evla = latitude, evlo=longitude)
return
def add_earthquake(self, longitude, latitude, depth, m_rr, m_tt, m_pp, m_tp, m_rt, m_rp):
"""Add earthquake to catalog
"""
self.events.add_earthquake(longitude=longitude, latitude=latitude, depth=depth,
m_rr=m_rr, m_tt=m_tt, m_pp=m_pp, m_tp=m_tp, m_rt=m_rt, m_rp=m_rp)
self._check_time_step(evla = latitude, evlo=longitude)
return
def _check_time_step(self, evla, evlo, vmin = 3.0):
"""Check whether the time step for seismogram is large enough
"""
minlat=self.config.mesh_min_latitude
maxlat=self.config.mesh_max_latitude
minlon=self.config.mesh_min_longitude
maxlon=self.config.mesh_max_longitude
num_timpstep = self.config.number_of_time_steps
dt = self.config.time_increment_in_s
dist1, az, baz=obspy.geodetics.base.gps2dist_azimuth(evla, evlo, minlat, minlon) # distance is in m
dist2, az, baz=obspy.geodetics.base.gps2dist_azimuth(evla, evlo, minlat, maxlon) # distance is in m
dist3, az, baz=obspy.geodetics.base.gps2dist_azimuth(evla, evlo, maxlat, minlon) # distance is in m
dist4, az, baz=obspy.geodetics.base.gps2dist_azimuth(evla, evlo, maxlat, maxlon) # distance is in m
distArr = np.array([dist1/1000., dist2/1000., dist3/1000., dist4/1000.])
distmax = distArr.max()
if distmax/vmin * 1.5 > dt * num_timpstep:
warnings.warn('Time length of seismogram is too short, recommended: '+
str(1.5*distmax/vmin)+' sec, actual: '+str(dt * num_timpstep)+' sec', UserWarning, stacklevel=1)
return
def set_config(self, num_timpstep, dt, nx_global, ny_global, nz_global, px, py, pz, minlat, maxlat, minlon, maxlon, zmin, zmax,
isdiss=True, model_type=3, simulation_type=0, output_folder='../OUTPUT', adjoint_output_folder='../OUTPUT/ADJOINT',
lpd=4, displacement_snapshot_sampling=100, output_displacement=1, samp_ad=15 ):
""" Set configuration for SES3D
=================================================================================================
Input Parameters:
num_timpstep - number of time step
dt - time interval
-------------------------------------------------------------------------------------------------
x : colatitude, y: longitude, z: depth
nx_global, ny_global, nz_global
- number of finite elements in x/y/z direction
px, py, pz - number of computational subdomain in x/y/z direction
-------------------------------------------------------------------------------------------------
minlat, maxlat - minimum/maximum latitude
minlon, maxlon - minimum/maximum longitude
zmin, zmax - minimum/maximum depth
isdiss - dissipation on/off
model_type - 1 D Earth model type (default = 3)
1. all-zero velocity and density model
2. PREMiso
3. all-zero velocity and density model with Q model QL6
4. modified PREMiso with 220km discontinuity replaced
by a linear gradient
7. ak135
simulation_type - simulation type (default = 0)
0: normal simulation; 1: adjoint forward; 2: adjoint reverse
lpd - Lagrange polynomial degree (default = 4)
samp_ad - sampling rate for adjoint field
=================================================================================================
"""
print 'ATTENTION: Have You Updated the SOURCE/ses3d_conf.h and recompile the code???!!!'
if simulation_type==0: self.config.simulation_type = "normal simulation"
elif simulation_type==1: self.config.simulation_type = "adjoint forward"
elif simulation_type==2: self.config.simulation_type = "adjoint reverse"
# SES3D specific configuration
self.config.output_folder = output_folder
self.config.lagrange_polynomial_degree = lpd
# Time configuration.
self.config.number_of_time_steps = num_timpstep
self.config.time_increment_in_s = dt
self.config.adjoint_forward_sampling_rate=samp_ad
self.config.displacement_snapshot_sampling = displacement_snapshot_sampling
self.config.output_displacement = output_displacement
if nx_global%px!=0 or ny_global%py!=0 or nz_global%pz!=0:
raise ValueError('nx_global/px, ny_global/py, nz_global/pz must ALL be integer!')
if int(nx_global/px)!=22 or int(ny_global/py)!=27 or int(nz_global/pz)!=7:
print 'ATTENTION: elements in x/y/z direction per processor is NOT default Value! Check Carefully before running!'
totalP=px*py*pz
if totalP%12!=0:
raise ValueError('Total number of processor must be 12N !')
print '====================== Number of Nodes needed at Janus is: %g ======================' %(totalP/12)
# SES3D specific discretization
self.config.nx_global = nx_global
self.config.ny_global = ny_global
self.config.nz_global = nz_global
self.config.px = px
self.config.py = py
self.config.pz = pz
# Configure the mesh.
self.config.mesh_min_latitude = minlat
self.config.mesh_max_latitude = maxlat
self.config.mesh_min_longitude = minlon
self.config.mesh_max_longitude = maxlon
self.config.mesh_min_depth_in_km = zmin
self.config.mesh_max_depth_in_km = zmax
self.config.adjoint_forward_wavefield_output_folder = adjoint_output_folder
self.model_type = model_type
self.config.is_dissipative = isdiss
self.CFLCondition( )
# # Define the rotation. Take care this is defined as the rotation of the
# # mesh. The data will be rotated in the opposite direction! The following
# # example will rotate the mesh 5 degrees southwards around the x-axis. For
# # a definition of the coordinate system refer to the rotations.py file. The
# # rotation is entirely optional.
# gen.config.rotation_angle_in_degree = 5.0
# gen.config.rotation_axis = [1.0, 0.0, 0.0]
# Define Q
return
def CFLCondition(self, C=0.35 ):
"""
Check Courant-Frieddrichs-Lewy stability condition
======================================================================================
Input Parameters:
C - Courant number (default = 0.35, normally 0.3~0.4)
======================================================================================
"""
if not os.path.isfile('./PREM.mod'):
raise NameError('PREM Model File NOT exist!')
InArr=np.loadtxt('./PREM.mod')
depth=InArr[:,1]
Vp=InArr[:,4]
dt = self.config.time_increment_in_s
minlat=self.config.mesh_min_latitude
maxlat=self.config.mesh_max_latitude
minlon=self.config.mesh_min_longitude
maxlon=self.config.mesh_max_longitude
nx_global = self.config.nx_global
ny_global = self.config.ny_global
nz_global = self.config.nz_global
NGLL = self.config.lagrange_polynomial_degree
zmin=self.config.mesh_min_depth_in_km
zmax=self.config.mesh_max_depth_in_km
maxabslat=max(abs(minlat), abs(maxlat))
Vpmax=Vp[depth>zmax][0]
dlat=(maxlat-minlat)/nx_global # x : colatitude
dlon=(maxlon-minlon)/ny_global
dz=(zmax-zmin)/nz_global
distEW, az, baz=obspy.geodetics.base.gps2dist_azimuth(maxabslat, 45, maxabslat, 45+dlon) # distance is in m
distEWmin=distEW/1000.*(6371.-zmax)/6371.
distNS, az, baz=obspy.geodetics.base.gps2dist_azimuth(maxabslat, 45, maxabslat+dlat, 45) # distance is in m
distNSmin=distNS/1000.*(6371.-zmax)/6371.
dtEW=C*distEWmin/Vpmax/NGLL
dtNS=C*distNSmin/Vpmax/NGLL
dtZ=C*dz/Vpmax/NGLL
print '=======================================================================' + \
'========================================================================='
if dt > dtEW or dt > dtNS or dt > dtZ:
raise ValueError('Time step violates Courant-Frieddrichs-Lewy Condition: ',dt, dtEW, dtNS, dtZ)
else:
print 'Time Step used:',dt, 'EW direction required:', dtEW, 'NS direction required:',dtNS, 'depth direction required:',dtZ
print '=======================================================================' + \
'========================================================================='
return
def WavelengthCondition(self, fmax=1.0/5.0, vmin=1.0, wpe=1.5): ### wpe = 1.5~2.0 is preferred
"""
Check minimum wavelength condition
==========================================================
Input Parameters:
fmax - maximum frequency
Vmin - minimum velocity
wpe - wavelength per element
==========================================================
"""
lamda=vmin/fmax
NGLL = self.config.lagrange_polynomial_degree
C=lamda*NGLL/5./wpe
minlat=self.config.mesh_min_latitude
maxlat=self.config.mesh_max_latitude
minlon=self.config.mesh_min_longitude
maxlon=self.config.mesh_max_longitude
minabslat=min(abs(minlat), abs(maxlat))
mindep=self.config.mesh_min_depth_in_km
maxdep=self.config.mesh_max_depth_in_km
nx_global=self.config.nx_global
ny_global=self.config.ny_global
nz_global=self.config.nz_global
dlat=(maxlat-minlat)/nx_global
dlon=(maxlon-minlon)/ny_global
dz=(maxdep-mindep)/nz_global
distEW, az, baz=obspy.geodetics.base.gps2dist_azimuth(minabslat, 45, minabslat, 45+dlon) # distance is in m
distNS, az, baz=obspy.geodetics.base.gps2dist_azimuth(minabslat, 45, minabslat+dlat, 45) # distance is in m
distEW=distEW/1000.
distNS=distNS/1000.
print '=======================================================================' + \
'========================================================================='
print 'Minimum wavelength condition number:',C, 'Element Size: dEW:',distEW, ' km, dNS: ', distNS,' km, dz:',dz
if dz> C or distEW > C or distNS > C:
raise ValueError('Minimum Wavelength Condition not satisfied!')
print '=======================================================================' + \
'========================================================================='
return
def add_stations(self, inSta):
"""Add station list
"""
try:
self.stalst= self.stalst + inSta
except TypeError:
SLst=stations.StaLst()
SLst.read(inSta)
self.stalst = self.stalst + SLst
return
def get_stf(self, stf, fmin=None, vmin = 1.0, fmax=None, plotflag=False):
"""
Get source time function and filter it according to fmin/fmax
==========================================================
Input Parameters:
stf - source time function
fmin/fmax - minimum/maximum frequency
plotflag - plot source time function or not
==========================================================
"""
if self.config.time_increment_in_s!=stf.stats.delta or self.config.number_of_time_steps != stf.stats.npts:
raise ValueError('Incompatible dt or npts in source time function!')
if fmin !=None:
stf.filter('highpass', freq=fmin, corners=4, zerophase=False)
if fmax !=None:
stf.filter('lowpass', freq=fmax, corners=5, zerophase=False)
else:
try:
fmax=stf.fcenter*2.0 ### should be 2.5 !!!
except:
raise AttributeError('Maximum frequency not specified!')
if plotflag==True:
stf.plotstf(fmax=fmax)
self.WavelengthCondition(fmax=fmax, vmin=vmin)
self.config.source_time_function = stf.data
return
def write(self, outdir, verbose=True):
"""Write input files(setup, event_x, event_list, recfile_x, stf) to given directory
"""
outdir=outdir
if not os.path.isdir(outdir):
os.makedirs(outdir)
self.events.write(outdir, config=self.config)
self.stalst.write(outdir)
stf_fname=outdir+'/stf'
# Note: header is mandatory for stf file!!!
np.savetxt(stf_fname, self.config.source_time_function, header = '\n \n \n')
if self.config.is_dissipative and ( not os.path.isfile(outdir+'/relax') ):
print outdir
raise AttributeError('relax file not exists!')
setup_file_template = (
"MODEL ==============================================================="
"================================================================="
"=====\n"
"{theta_min:<44.6f}! theta_min (colatitude) in degrees\n"
"{theta_max:<44.6f}! theta_max (colatitude) in degrees\n"
"{phi_min:<44.6f}! phi_min (longitude) in degrees\n"
"{phi_max:<44.6f}! phi_max (longitude) in degrees\n"
"{z_min:<44.6f}! z_min (radius) in m\n"
"{z_max:<44.6f}! z_max (radius) in m\n"
"{is_diss:<44d}! is_diss\n"
"{model_type:<44d}! model_type\n"
"COMPUTATIONAL SETUP (PARALLELISATION) ==============================="
"================================================================="
"=====\n"
"{nx_global:<44d}! nx_global, "
"(nx_global+px = global # elements in theta direction)\n"
"{ny_global:<44d}! ny_global, "
"(ny_global+py = global # elements in phi direction)\n"
"{nz_global:<44d}! nz_global, "
"(nz_global+pz = global # of elements in r direction)\n"
"{lpd:<44d}! lpd, LAGRANGE polynomial degree\n"
"{px:<44d}! px, processors in theta direction\n"
"{py:<44d}! py, processors in phi direction\n"
"{pz:<44d}! pz, processors in r direction\n"
"ADJOINT PARAMETERS =================================================="
"================================================================="
"=====\n"
"{adjoint_flag:<44d}! adjoint_flag (0=normal simulation, "
"1=adjoint forward, 2=adjoint reverse)\n"
"{samp_ad:<44d}! samp_ad, sampling rate of forward field\n"
"{adjoint_wavefield_folder}")
EARTH_RADIUS = 6371 * 1000.
adjointdict={'normal simulation': 0, 'adjoint forward': 1, 'adjoint reverse': 2}
setup_file = setup_file_template.format(
# Colatitude! Swaps min and max.
theta_min=rotations.lat2colat(float( self.config.mesh_max_latitude)),
theta_max=rotations.lat2colat(float( self.config.mesh_min_latitude)),
phi_min=float(self.config.mesh_min_longitude),
phi_max=float(self.config.mesh_max_longitude),
# Min/max radius and depth are inverse to each other.
z_min=EARTH_RADIUS - (float(self.config.mesh_max_depth_in_km) * 1000.0),
z_max=EARTH_RADIUS - (float(self.config.mesh_min_depth_in_km) * 1000.0),
is_diss=1 if self.config.is_dissipative else 0,
model_type=3,
lpd=int(self.config.lagrange_polynomial_degree),
# Computation setup.
nx_global=self.config.nx_global,
ny_global=self.config.ny_global,
nz_global=self.config.nz_global,
px=self.config.px,
py=self.config.py,
pz=self.config.pz,
adjoint_flag=adjointdict[self.config.simulation_type],
samp_ad=self.config.adjoint_forward_sampling_rate,
adjoint_wavefield_folder=self.config.adjoint_forward_wavefield_output_folder)
setup_fname=outdir+'/setup'
with open(setup_fname, 'wb') as f:
f.writelines(setup_file)
return