pySATSI 0.2.0__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.
- pySATSI/__init__.py +1 -0
- pySATSI/functions/__init__.py +1 -0
- pySATSI/functions/bootmech.py +481 -0
- pySATSI/functions/checkerboard.py +115 -0
- pySATSI/functions/colormaps/colorwheel.txt +256 -0
- pySATSI/functions/colormaps/rainbow.txt +256 -0
- pySATSI/functions/default_params.py +124 -0
- pySATSI/functions/file_io.py +597 -0
- pySATSI/functions/fun.py +337 -0
- pySATSI/functions/gridding.py +562 -0
- pySATSI/functions/knee.py +183 -0
- pySATSI/functions/mech.py +305 -0
- pySATSI/functions/plotting.py +576 -0
- pySATSI/functions/stress.py +317 -0
- pySATSI/pySATSI.py +179 -0
- pysatsi-0.2.0.dist-info/METADATA +72 -0
- pysatsi-0.2.0.dist-info/RECORD +20 -0
- pysatsi-0.2.0.dist-info/WHEEL +4 -0
- pysatsi-0.2.0.dist-info/entry_points.txt +2 -0
- pysatsi-0.2.0.dist-info/licenses/LICENSE.md +46 -0
pySATSI/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Functions used for bootstrapping and uncertainty computations.
|
|
3
|
+
|
|
4
|
+
Author: Robert J. Skoumal (rskoumal@usgs.gov)
|
|
5
|
+
'''
|
|
6
|
+
|
|
7
|
+
# Standard libraries
|
|
8
|
+
import multiprocessing
|
|
9
|
+
|
|
10
|
+
# External libraries
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import numpy as np
|
|
13
|
+
import scipy.stats as st
|
|
14
|
+
rng=np.random.default_rng(123) # Used to produce reproducable bootstrapped results.
|
|
15
|
+
|
|
16
|
+
# Local libraries
|
|
17
|
+
import functions.stress as stress
|
|
18
|
+
import functions.fun as fun
|
|
19
|
+
import functions.knee as knee
|
|
20
|
+
|
|
21
|
+
def compute_boot(mech_df,damp_df,p_dict):
|
|
22
|
+
'''
|
|
23
|
+
nboot: Number of bootstrap iterations to perform.
|
|
24
|
+
For nboot==1 or the first boostrap iteration, it will compute the stresses using the provided nodal planes and events.
|
|
25
|
+
For nboot>1, it will randomize according to the $randomize_events and $randomize_nodalplane options.
|
|
26
|
+
fracplane: Chance the nodal plane is the fault plane. Either a float or an array of floats
|
|
27
|
+
'''
|
|
28
|
+
|
|
29
|
+
num_iterations=p_dict['niterations']
|
|
30
|
+
if num_iterations<1:
|
|
31
|
+
num_iterations=1
|
|
32
|
+
|
|
33
|
+
for iter_x in range(num_iterations):
|
|
34
|
+
boot_stress_df=[]
|
|
35
|
+
if p_dict['num_cpus']==1: # Runs bootstrapping in serial
|
|
36
|
+
for boot_x in range(p_dict['nboot']):
|
|
37
|
+
select_mech_df=mech_df.copy()
|
|
38
|
+
if boot_x!=0:
|
|
39
|
+
select_mech_df=randomize_mech(select_mech_df,p_dict)
|
|
40
|
+
boot_stress_df.append(stress.slickenside(select_mech_df,damp_df,p_dict['damp_param'],p_dict,boot_x=boot_x,iter_x=iter_x))
|
|
41
|
+
else: # Runs bootstrapping in parallel
|
|
42
|
+
print('\tComputing bootstraps in parallel...')
|
|
43
|
+
pool=multiprocessing.Pool(processes=p_dict['num_cpus'])
|
|
44
|
+
async_results=[]
|
|
45
|
+
for boot_x in range(p_dict['nboot']):
|
|
46
|
+
select_mech_df=mech_df.copy()
|
|
47
|
+
if boot_x!=0:
|
|
48
|
+
select_mech_df=randomize_mech(select_mech_df,p_dict)
|
|
49
|
+
async_results.append(pool.apply_async(stress.slickenside,
|
|
50
|
+
(select_mech_df,damp_df,p_dict['damp_param'],p_dict),dict(boot_x=boot_x,iter_x=iter_x)))
|
|
51
|
+
pool.close()
|
|
52
|
+
pool.join()
|
|
53
|
+
for result in async_results:
|
|
54
|
+
boot_stress_df.append(result.get())
|
|
55
|
+
|
|
56
|
+
boot_stress_df=pd.concat(boot_stress_df).reset_index(drop=True)
|
|
57
|
+
|
|
58
|
+
# Use fault instability to inform fault plane
|
|
59
|
+
if p_dict['niterations']>0:
|
|
60
|
+
if (iter_x>=(num_iterations-2)) and (p_dict['final_instability']==True):
|
|
61
|
+
instability_weight=1.
|
|
62
|
+
iteration_weight=1.
|
|
63
|
+
damp_df=pd.DataFrame(columns=['cluster_id1','cluster_id2'])
|
|
64
|
+
else:
|
|
65
|
+
instability_weight=p_dict['instability_weight']
|
|
66
|
+
iteration_weight=p_dict['iteration_weight']
|
|
67
|
+
|
|
68
|
+
mech_df['instability'],mech_df['aux_instability']=fault_instability(boot_stress_df,mech_df,p_dict)
|
|
69
|
+
Idiff=mech_df['instability'].values**2-mech_df['aux_instability'].values**2
|
|
70
|
+
abs_Idiff=np.abs(Idiff)
|
|
71
|
+
|
|
72
|
+
# Determine the fault probability
|
|
73
|
+
old_fault_probability=mech_df['fault_probability'].values
|
|
74
|
+
mech_df['fault_probability']=100*(iteration_weight*
|
|
75
|
+
(((Idiff/(instability_weight*abs_Idiff-instability_weight+1)+1)/2)-old_fault_probability/100) +
|
|
76
|
+
old_fault_probability/100)
|
|
77
|
+
|
|
78
|
+
# Checks for convergence
|
|
79
|
+
mean_prob_diff=np.mean(np.abs(mech_df['fault_probability'].values-old_fault_probability))
|
|
80
|
+
print('\t\tMean change of fault probability: {}%'.format(mean_prob_diff.round(2)))
|
|
81
|
+
if mean_prob_diff<p_dict['convergence_threshold']:
|
|
82
|
+
if iter_x<(num_iterations-1):
|
|
83
|
+
print('\tReached the convergence threshold of {}%. Stopping iterations.'.format(p_dict['convergence_threshold']))
|
|
84
|
+
break
|
|
85
|
+
if p_dict['iterative_damping']:
|
|
86
|
+
if iter_x<(num_iterations-1):
|
|
87
|
+
iteration_damp_misfit_df,p_dict['damp_param']=knee.find_damp(mech_df,damp_df,p_dict)
|
|
88
|
+
if p_dict['iterative_damping']:
|
|
89
|
+
return boot_stress_df,iteration_damp_misfit_df
|
|
90
|
+
else:
|
|
91
|
+
return boot_stress_df,None
|
|
92
|
+
|
|
93
|
+
def randomize_mech(mech_df,p_dict):
|
|
94
|
+
'''
|
|
95
|
+
Randomizes the nodal plane for the mechanisms
|
|
96
|
+
|
|
97
|
+
Input:
|
|
98
|
+
mech_df: DataFrame of [ddir, ddir, dip, dip2, rake1, rake2, cluster_id]
|
|
99
|
+
fracplane: Percentage that the nodal plane is the fault plane. Either a float or an array of floats
|
|
100
|
+
randomize_events: Whether the selection of events are randomized, boolean.
|
|
101
|
+
randomize_nodalplane: Whether the selection of nodal planes are randomized, boolean.
|
|
102
|
+
Output:
|
|
103
|
+
select_mech_df: DataFrame containing randomized nodal planes
|
|
104
|
+
'''
|
|
105
|
+
random_mech_df=mech_df.copy()
|
|
106
|
+
nobs=len(random_mech_df)
|
|
107
|
+
if p_dict['randomize_events']: # Randomizes which events are selected
|
|
108
|
+
rand1=rng.choice(nobs,nobs)
|
|
109
|
+
random_mech_df=random_mech_df.copy().loc[rand1,:].reset_index(drop=True)
|
|
110
|
+
|
|
111
|
+
# Applies noise and randomizes which nodal plane is the fault plane
|
|
112
|
+
if random_mech_df['fault_plane_uncertainty'].any():
|
|
113
|
+
random_mech_df=mech_noise(random_mech_df)
|
|
114
|
+
elif p_dict['randomize_nodalplane']:
|
|
115
|
+
random_mech_df['ddir2'],random_mech_df['dip2'],random_mech_df['rake2']=fun.aux_plane_ddir(random_mech_df['ddir'].values,random_mech_df['dip'].values,random_mech_df['rake'].values)
|
|
116
|
+
rand2=rng.uniform(size=nobs)*100
|
|
117
|
+
if 'fault_probability' in random_mech_df:
|
|
118
|
+
ind_rand2=np.where(rand2>random_mech_df['fault_probability'])[0]
|
|
119
|
+
else:
|
|
120
|
+
ind_rand2=np.where(rand2>p_dict['fracplane'])[0]
|
|
121
|
+
random_mech_df.loc[ind_rand2,['ddir','dip','rake']]=random_mech_df.loc[ind_rand2,['ddir2','dip2','rake2']].values
|
|
122
|
+
random_mech_df=random_mech_df.drop(columns=['ddir2','dip2','rake2'])
|
|
123
|
+
|
|
124
|
+
return random_mech_df
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def avguncert_azimuth_plunge(azimuth,plunge,round=1,confidence_thresh=95.):
|
|
128
|
+
'''
|
|
129
|
+
Given azimuths and plunges (in degrees), computes the eigenvectors of the
|
|
130
|
+
covariance matrix and returns the mean azimuth in plunge (in degrees).
|
|
131
|
+
'''
|
|
132
|
+
|
|
133
|
+
# Converts plunge,azimuth (degrees) to lon,lat (radians)
|
|
134
|
+
lat=np.radians(90-plunge)
|
|
135
|
+
lon=0
|
|
136
|
+
theta=np.radians(azimuth)
|
|
137
|
+
|
|
138
|
+
# Converts lon,lat to xyz
|
|
139
|
+
x=np.cos(lat)*np.cos(lon)
|
|
140
|
+
y=np.cos(lat)*np.sin(lon)
|
|
141
|
+
z=np.sin(lat)
|
|
142
|
+
|
|
143
|
+
# Rotate lon,lat coords by theta degrees
|
|
144
|
+
y_2=y*np.cos(theta)+z*np.sin(theta)
|
|
145
|
+
z_2=-y*np.sin(theta)+z*np.cos(theta)
|
|
146
|
+
r=np.sqrt(x**2+y_2**2+z_2**2)
|
|
147
|
+
lat=np.arcsin(z_2/r)
|
|
148
|
+
lon=np.arctan2(y_2,x)
|
|
149
|
+
|
|
150
|
+
# Computes the antipodes
|
|
151
|
+
x=np.cos(lat)*np.cos(lon)
|
|
152
|
+
y=np.cos(lat)*np.sin(lon)
|
|
153
|
+
z=np.sin(lat)
|
|
154
|
+
r=np.sqrt(x**2+y**2+z**2)
|
|
155
|
+
lat_antipode=np.arcsin(-z/r)
|
|
156
|
+
lon_antipode=np.arctan2(-y,-x)
|
|
157
|
+
|
|
158
|
+
# Concats the antipodes to the lon,lat arrays
|
|
159
|
+
lon,lat=np.hstack([lon,lon_antipode]),np.hstack([lat,lat_antipode])
|
|
160
|
+
xyz=np.zeros((len(lat),3))
|
|
161
|
+
xyz[:,0]=np.cos(lat)*np.cos(lon)
|
|
162
|
+
xyz[:,1]=np.cos(lat)*np.sin(lon)
|
|
163
|
+
xyz[:,2]=np.sin(lat)
|
|
164
|
+
|
|
165
|
+
# Computes eigenvectors
|
|
166
|
+
cov=np.cov(xyz.T)
|
|
167
|
+
eigvalues,eigvectors=np.linalg.eigh(cov)
|
|
168
|
+
sort_index=eigvalues.argsort()
|
|
169
|
+
vecs=eigvectors[:,sort_index]
|
|
170
|
+
x,y,z=vecs[:,-1]
|
|
171
|
+
|
|
172
|
+
# Calculate azimuth and plunge (in radians)
|
|
173
|
+
azimuth_rads=np.arctan2(z,y)
|
|
174
|
+
r=np.sqrt(x**2+y**2+z**2)
|
|
175
|
+
if r==0:
|
|
176
|
+
plunge_rads=np.arcsin(1)
|
|
177
|
+
else:
|
|
178
|
+
plunge_rads=np.arcsin(x/r)
|
|
179
|
+
|
|
180
|
+
# Convert azimuth and plunge to degrees
|
|
181
|
+
mean_plunge=np.degrees(plunge_rads)
|
|
182
|
+
mean_azimuth=np.degrees(azimuth_rads)
|
|
183
|
+
mean_azimuth=90-mean_azimuth
|
|
184
|
+
if mean_azimuth<0:
|
|
185
|
+
mean_azimuth+=360
|
|
186
|
+
|
|
187
|
+
# If upwards plunge, get antipode
|
|
188
|
+
if mean_plunge<0:
|
|
189
|
+
mean_plunge*=-1
|
|
190
|
+
mean_azimuth-=180
|
|
191
|
+
if mean_azimuth<0:
|
|
192
|
+
mean_azimuth+=360
|
|
193
|
+
|
|
194
|
+
mean_azimuth=np.round(mean_azimuth,round)
|
|
195
|
+
mean_plunge=np.round(mean_plunge,round)
|
|
196
|
+
|
|
197
|
+
# Calculate confidence bounds
|
|
198
|
+
n_measurements=len(azimuth)
|
|
199
|
+
j=int(np.floor((n_measurements-1)*((100.-confidence_thresh)/100.)))
|
|
200
|
+
dot_conf=np.dot(xyz[:int(xyz.shape[0]/2)],np.asarray([x,y,z]))
|
|
201
|
+
ind_conf=np.argsort(np.abs(dot_conf))[j]
|
|
202
|
+
conf_azimuth=azimuth[ind_conf]
|
|
203
|
+
conf_plunge=plunge[ind_conf]
|
|
204
|
+
|
|
205
|
+
if dot_conf[ind_conf]<0:
|
|
206
|
+
conf_azimuth-=180
|
|
207
|
+
|
|
208
|
+
uncert_azimuth=fun.az_diff_180(mean_azimuth,conf_azimuth)
|
|
209
|
+
uncert_plunge=np.abs(mean_plunge-conf_plunge)
|
|
210
|
+
|
|
211
|
+
# Round uncertainties
|
|
212
|
+
uncert_azimuth=np.round(uncert_azimuth,round)
|
|
213
|
+
uncert_plunge=np.round(uncert_plunge,round)
|
|
214
|
+
|
|
215
|
+
return mean_azimuth,mean_plunge,uncert_azimuth,uncert_plunge
|
|
216
|
+
|
|
217
|
+
def uncert(boot_stress_df,p_dict):
|
|
218
|
+
'''
|
|
219
|
+
Calculates the average and variability range of the bootstrapped results
|
|
220
|
+
'''
|
|
221
|
+
# Group bootstrapped results by cluster_id
|
|
222
|
+
boot_stress_df=boot_stress_df.sort_values(by=['cluster_id','boot'])
|
|
223
|
+
|
|
224
|
+
# If no bootstrapping was done, no uncertainties need to be calculated
|
|
225
|
+
if boot_stress_df['boot'].max()==0:
|
|
226
|
+
boot_uncert_df=boot_stress_df[['cluster_id','phi','az1','pl1','az2','pl2','az3','pl3',]]
|
|
227
|
+
else: # Calculates uncertainties
|
|
228
|
+
group_boot_stress_df=boot_stress_df.groupby(by='cluster_id')
|
|
229
|
+
r_keys=list(group_boot_stress_df.groups.keys())
|
|
230
|
+
|
|
231
|
+
# Create empty uncert dataframe
|
|
232
|
+
boot_uncert_df=pd.DataFrame(index=np.arange(len(r_keys)),
|
|
233
|
+
columns=['cluster_id','phi','uncert_phi',
|
|
234
|
+
'az1','az1_uncert','pl1','pl1_uncert',
|
|
235
|
+
'az2','az2_uncert','pl2','pl2_uncert',
|
|
236
|
+
'az3','az3_uncert','pl3','pl3_uncert'],
|
|
237
|
+
dtype=float)
|
|
238
|
+
boot_uncert_df['cluster_id']=r_keys
|
|
239
|
+
boot_uncert_df['cluster_id']=boot_uncert_df['cluster_id'].astype(int)
|
|
240
|
+
|
|
241
|
+
# Loop over each cluster
|
|
242
|
+
for r_x,r_key in enumerate(r_keys):
|
|
243
|
+
stress_df=group_boot_stress_df.get_group(r_key)
|
|
244
|
+
|
|
245
|
+
# If there's only one record, no need to calculate uncertainties
|
|
246
|
+
if len(stress_df)==1:
|
|
247
|
+
boot_uncert_df.loc[r_x,['phi','az1','pl1','az2','pl2','az3','pl3']]=stress_df.iloc[0][['phi','az1','pl1','az2','pl2','az3','pl3']]
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
# Computes mean azimuth/plunge using the eigenvectors of the covariance matrix
|
|
251
|
+
boot_uncert_df.loc[r_x,['az1','pl1','az1_uncert','pl1_uncert']]=avguncert_azimuth_plunge(
|
|
252
|
+
stress_df['az1'].values,stress_df['pl1'].values,
|
|
253
|
+
round=p_dict['angle_precision'],confidence_thresh=p_dict['confidence'])
|
|
254
|
+
boot_uncert_df.loc[r_x,['az2','pl2','az2_uncert','pl2_uncert']]=avguncert_azimuth_plunge(
|
|
255
|
+
stress_df['az2'].values,stress_df['pl2'].values,
|
|
256
|
+
round=p_dict['angle_precision'],confidence_thresh=p_dict['confidence'])
|
|
257
|
+
boot_uncert_df.loc[r_x,['az3','pl3','az3_uncert','pl3_uncert']]=avguncert_azimuth_plunge(
|
|
258
|
+
stress_df['az3'].values,stress_df['pl3'].values,
|
|
259
|
+
round=p_dict['angle_precision'],confidence_thresh=p_dict['confidence'])
|
|
260
|
+
|
|
261
|
+
# Computes phi stats
|
|
262
|
+
boot_uncert_df.loc[r_x,'phi']=np.round(stress_df['phi'].mean(),p_dict['vector_precision'])
|
|
263
|
+
if len(stress_df['phi'].unique())>1:
|
|
264
|
+
tmp_interval=st.t.interval(p_dict['confidence']/100,stress_df.shape[0]-1,loc=boot_uncert_df.loc[r_x,['phi']],scale=st.sem(stress_df['phi'].values))
|
|
265
|
+
boot_uncert_df.loc[r_x,'uncert_phi']=np.round(np.max(np.abs(boot_uncert_df.loc[r_x,'phi']-tmp_interval)),p_dict['vector_precision'])
|
|
266
|
+
else:
|
|
267
|
+
boot_uncert_df.loc[r_x,'uncert_phi']=0.
|
|
268
|
+
|
|
269
|
+
return boot_uncert_df
|
|
270
|
+
|
|
271
|
+
def mech_noise(select_mech_df):
|
|
272
|
+
'''
|
|
273
|
+
Adds random noise to the mechanism solutions
|
|
274
|
+
'''
|
|
275
|
+
noise_mech_df=select_mech_df.copy()
|
|
276
|
+
strike_r=np.radians(noise_mech_df['ddir'].values-90)
|
|
277
|
+
dip_r=np.radians(noise_mech_df['dip'].values)
|
|
278
|
+
rake_r=np.radians(noise_mech_df['rake'].values)
|
|
279
|
+
|
|
280
|
+
num_mechs=len(strike_r)
|
|
281
|
+
|
|
282
|
+
# Fault normals
|
|
283
|
+
n=np.zeros((num_mechs,3))
|
|
284
|
+
n[:,0]=-np.sin(dip_r)*np.sin(strike_r)
|
|
285
|
+
n[:,1]=np.sin(dip_r)*np.cos(strike_r)
|
|
286
|
+
n[:,2]=-np.cos(dip_r)
|
|
287
|
+
|
|
288
|
+
# Slip direction
|
|
289
|
+
u=np.zeros((num_mechs,3))
|
|
290
|
+
u[:,0]=np.cos(rake_r)*np.cos(strike_r) + np.cos(dip_r)*np.sin(rake_r)*np.sin(strike_r)
|
|
291
|
+
u[:,1]=np.cos(rake_r)*np.sin(strike_r) - np.cos(dip_r)*np.sin(rake_r)*np.cos(strike_r)
|
|
292
|
+
u[:,2]=-np.sin(rake_r)*np.sin(dip_r)
|
|
293
|
+
|
|
294
|
+
rand2=rng.uniform(size=num_mechs)*100
|
|
295
|
+
if 'fault_probability' in noise_mech_df:
|
|
296
|
+
tmp_bool=rand2<noise_mech_df['fault_probability'].values
|
|
297
|
+
else:
|
|
298
|
+
tmp_bool=rand2<50
|
|
299
|
+
i0_ind=np.where(tmp_bool)[0]
|
|
300
|
+
i1_ind=np.where(~tmp_bool)[0]
|
|
301
|
+
|
|
302
|
+
b=np.zeros((len(strike_r),3))
|
|
303
|
+
b[i0_ind]=np.cross(n[i0_ind],u[i0_ind])
|
|
304
|
+
b[i1_ind]=np.cross(n[i1_ind],u[i1_ind])
|
|
305
|
+
|
|
306
|
+
fault_plane_uncertainty=noise_mech_df['fault_plane_uncertainty'].values
|
|
307
|
+
if 'aux_plane_uncertainty' in noise_mech_df.columns:
|
|
308
|
+
fault_plane_uncertainty[i1_ind]=noise_mech_df['aux_plane_uncertainty'].values[i1_ind]
|
|
309
|
+
|
|
310
|
+
# Random fault normal
|
|
311
|
+
n_deviation_random=np.radians(fault_plane_uncertainty*np.random.randn(num_mechs))
|
|
312
|
+
b_deviation_random=np.radians(fault_plane_uncertainty*np.random.randn(num_mechs))
|
|
313
|
+
|
|
314
|
+
# Random noisy azimuth
|
|
315
|
+
n_azimuth_random=np.pi*2*np.random.random(num_mechs)
|
|
316
|
+
b_azimuth_random=np.pi*2*np.random.random(num_mechs)
|
|
317
|
+
|
|
318
|
+
# Applies random noise to n
|
|
319
|
+
n_perpendicular=u*np.sin(n_azimuth_random)[:,np.newaxis]+b*np.cos(n_azimuth_random)[:,np.newaxis]
|
|
320
|
+
n_noise_component=n_perpendicular*np.sin(n_deviation_random)[:,np.newaxis]
|
|
321
|
+
n_noisy=n+n_noise_component
|
|
322
|
+
n_noisy=n_noisy/np.linalg.norm(n_noisy,2,axis=1)[:,np.newaxis]
|
|
323
|
+
|
|
324
|
+
# Applies random noise to b
|
|
325
|
+
b_perpendicular=n_noisy*np.sin(b_azimuth_random)[:,np.newaxis]+u*np.cos(b_azimuth_random)[:,np.newaxis]
|
|
326
|
+
b_noise_component=b_perpendicular*np.sin(b_deviation_random)[:,np.newaxis]
|
|
327
|
+
b_noisy=b+b_noise_component
|
|
328
|
+
b_noisy=b_noisy/np.linalg.norm(b_noisy,2,axis=1)[:,np.newaxis]
|
|
329
|
+
|
|
330
|
+
# Determines noisy slip direction
|
|
331
|
+
u_noisy=np.cross(n_noisy,b_noisy)
|
|
332
|
+
u_noisy*=np.sign(np.sum(u*u_noisy,axis=1))[:,np.newaxis]
|
|
333
|
+
|
|
334
|
+
n=n_noisy.copy()
|
|
335
|
+
u=u_noisy.copy()
|
|
336
|
+
n[i1_ind]=u_noisy[i1_ind]
|
|
337
|
+
u[i1_ind]=n_noisy[i1_ind]
|
|
338
|
+
|
|
339
|
+
n1=n.copy()
|
|
340
|
+
u1=u.copy()
|
|
341
|
+
n2=u.copy()
|
|
342
|
+
u2=n.copy()
|
|
343
|
+
|
|
344
|
+
# Ensures vertical components are negative
|
|
345
|
+
pos_n1_ind=np.where(n1[:,2]>0)[0]
|
|
346
|
+
n1[pos_n1_ind]*=-1
|
|
347
|
+
u1[pos_n1_ind]*=-1
|
|
348
|
+
pos_n2_ind=np.where(n2[:,2]>0)[0]
|
|
349
|
+
n2[pos_n2_ind]*=-1
|
|
350
|
+
u2[pos_n2_ind]*=-1
|
|
351
|
+
|
|
352
|
+
# Determine strike, dip, rake of noisy mech
|
|
353
|
+
r_dip=np.arccos(-n1[:,2])
|
|
354
|
+
r_strike=np.arcsin(-n1[:,0]/np.sqrt(n1[:,0]**2+n1[:,1]**2))
|
|
355
|
+
dip=np.degrees(r_dip)
|
|
356
|
+
strike=np.degrees(r_strike)
|
|
357
|
+
rake=np.degrees(np.arcsin(-u1[:,2]/np.sin(r_dip)))
|
|
358
|
+
|
|
359
|
+
# Determining quadrants
|
|
360
|
+
n1_quad_ind=np.where(n1[:,1]<0)[0]
|
|
361
|
+
strike[n1_quad_ind]=180-strike[n1_quad_ind]
|
|
362
|
+
cos_rake=u1[:,0]*np.cos(np.radians(strike))+u1[:,1]*np.sin(np.radians(strike))
|
|
363
|
+
u1_quad_ind=np.where(cos_rake<0)[0]
|
|
364
|
+
rake[u1_quad_ind]=180-rake[u1_quad_ind]
|
|
365
|
+
|
|
366
|
+
strike[strike<0]+=360
|
|
367
|
+
rake[rake<-180]+=360
|
|
368
|
+
rake[rake>180]-=360
|
|
369
|
+
|
|
370
|
+
noise_mech_df['ddir']=(strike+90)%360
|
|
371
|
+
noise_mech_df['dip']=dip
|
|
372
|
+
noise_mech_df['rake']=rake
|
|
373
|
+
|
|
374
|
+
return noise_mech_df
|
|
375
|
+
|
|
376
|
+
def fault_instability(boot_stress_df,mech_df,p_dict):
|
|
377
|
+
'''
|
|
378
|
+
Calculates the fault instability for the primary and auxiliary
|
|
379
|
+
nodal planes following the instability parameter of Vavryčuk (2014).
|
|
380
|
+
Takes the median instability considering the provided friction values.
|
|
381
|
+
'''
|
|
382
|
+
group_mech_df=mech_df.groupby(by='cluster_id')
|
|
383
|
+
group_boot_stress_df=boot_stress_df.groupby(by='cluster_id')
|
|
384
|
+
instability=np.zeros(mech_df.shape[0]);instability[:]=np.nan
|
|
385
|
+
aux_instability=np.zeros(mech_df.shape[0]);aux_instability[:]=np.nan
|
|
386
|
+
|
|
387
|
+
for cluster_id in list(group_mech_df.groups.keys()):
|
|
388
|
+
cluster_mech_df=group_mech_df.get_group(cluster_id)
|
|
389
|
+
cluster_boot_stress_df=group_boot_stress_df.get_group(cluster_id)
|
|
390
|
+
|
|
391
|
+
ddir=cluster_mech_df['ddir'].values
|
|
392
|
+
dip=cluster_mech_df['dip'].values
|
|
393
|
+
rake=cluster_mech_df['rake'].values
|
|
394
|
+
|
|
395
|
+
# Determines aux nodal plane
|
|
396
|
+
aux_ddir,aux_dip,aux_rake=fun.aux_plane_ddir(ddir,dip,rake)
|
|
397
|
+
|
|
398
|
+
# Converts ddir,dip to radians
|
|
399
|
+
z=np.radians(ddir)
|
|
400
|
+
dip[dip==90]=89.99999
|
|
401
|
+
z2=np.radians(dip)
|
|
402
|
+
|
|
403
|
+
# Converts aux ddir,dip to radians
|
|
404
|
+
aux_z=np.radians(aux_ddir)
|
|
405
|
+
aux_ddir[aux_ddir==90]=89.99999
|
|
406
|
+
aux_z2=np.radians(aux_dip)
|
|
407
|
+
|
|
408
|
+
# Normal vector to plane
|
|
409
|
+
normal=np.zeros((len(z),3))
|
|
410
|
+
normal[:,0]=np.sin(z)*np.sin(z2)
|
|
411
|
+
normal[:,1]=np.cos(z)*np.sin(z2)
|
|
412
|
+
normal[:,2]=np.cos(z2)
|
|
413
|
+
|
|
414
|
+
# Normal vector to aux plane
|
|
415
|
+
aux_normal=np.zeros((len(aux_z),3))
|
|
416
|
+
aux_normal[:,0]=np.sin(aux_z)*np.sin(aux_z2)
|
|
417
|
+
aux_normal[:,1]=np.cos(aux_z)*np.sin(aux_z2)
|
|
418
|
+
aux_normal[:,2]=np.cos(aux_z2)
|
|
419
|
+
|
|
420
|
+
# Determines stress tensor
|
|
421
|
+
tau=np.zeros((len(cluster_boot_stress_df),3,3))
|
|
422
|
+
tau[:,0,0]=cluster_boot_stress_df.loc[:,'st00']
|
|
423
|
+
tau[:,0,1]=cluster_boot_stress_df.loc[:,'st01']
|
|
424
|
+
tau[:,0,2]=cluster_boot_stress_df.loc[:,'st02']
|
|
425
|
+
tau[:,1,1]=cluster_boot_stress_df.loc[:,'st11']
|
|
426
|
+
tau[:,1,2]=cluster_boot_stress_df.loc[:,'st12']
|
|
427
|
+
tau[:,2,2]=cluster_boot_stress_df.loc[:,'st22']
|
|
428
|
+
tau[:,1,0]=tau[:,0,1]
|
|
429
|
+
tau[:,2,0]=tau[:,0,2]
|
|
430
|
+
tau[:,2,1]=tau[:,1,2]
|
|
431
|
+
tau=np.sum(tau,axis=0)
|
|
432
|
+
tau=tau/np.linalg.norm(tau,2)
|
|
433
|
+
tau_rot=tau.copy()
|
|
434
|
+
tau_rot[0,0]=tau[1,1]
|
|
435
|
+
tau_rot[1,1]=tau[0,0]
|
|
436
|
+
tau_rot[0,2]=tau[1,2]*-1
|
|
437
|
+
tau_rot[1,2]=tau[0,2]*-1
|
|
438
|
+
tau_rot[1,0]=tau_rot[0,1]
|
|
439
|
+
tau_rot[2,0]=tau_rot[0,2]
|
|
440
|
+
tau_rot[2,1]=tau_rot[1,2]
|
|
441
|
+
tau=tau_rot.copy()
|
|
442
|
+
|
|
443
|
+
lam,vecs=np.linalg.eig(tau)
|
|
444
|
+
|
|
445
|
+
# Sort eigenvalues
|
|
446
|
+
sort_ind=lam.argsort()
|
|
447
|
+
lam=lam[sort_ind]
|
|
448
|
+
vecs=vecs[:,sort_ind]
|
|
449
|
+
|
|
450
|
+
# Shape ratio
|
|
451
|
+
shape_ratio=(lam[0]-lam[1])/(lam[0]-lam[2])
|
|
452
|
+
|
|
453
|
+
normal=normal[:,[1,0,2]]
|
|
454
|
+
normal[:,2]*=-1
|
|
455
|
+
aux_normal=aux_normal[:,[1,0,2]]
|
|
456
|
+
aux_normal[:,2]*=-1
|
|
457
|
+
|
|
458
|
+
# Fault and aux plane normals * principal stresses
|
|
459
|
+
n=np.sum(normal[:,:,np.newaxis]*vecs,axis=1)
|
|
460
|
+
aux_n=np.sum(aux_normal[:,:,np.newaxis]*vecs,axis=1)
|
|
461
|
+
|
|
462
|
+
# Shear and normal traction along plane (Vavryčuk EQ 17,18)
|
|
463
|
+
tau_shear=np.sqrt(n[:,0]**2+(1-2*shape_ratio)**2*n[:,1]**2.+n[:,2]**2-(n[:,0]**2+(1-2*shape_ratio)*n[:,1]**2-n[:,2]**2)**2)
|
|
464
|
+
tau_normal=(n[:,0]**2+(1-2*shape_ratio)*n[:,1]**2-n[:,2]**2)
|
|
465
|
+
|
|
466
|
+
# Shear and normal traction along aux plane (Vavryčuk EQ 17,18)
|
|
467
|
+
aux_tau_shear=np.sqrt(aux_n[:,0]**2+(1-2*shape_ratio)**2*aux_n[:,1]**2.+aux_n[:,2]**2-(aux_n[:,0]**2+(1-2*shape_ratio)*aux_n[:,1]**2-aux_n[:,2]**2)**2)
|
|
468
|
+
aux_tau_normal=(aux_n[:,0]**2+(1-2*shape_ratio)*aux_n[:,1]**2-aux_n[:,2]**2)
|
|
469
|
+
|
|
470
|
+
# Computes instability for plane (Vavryčuk EQ 16)
|
|
471
|
+
cluster_instability=(tau_shear[:,np.newaxis]-p_dict['friction']*(tau_normal[:,np.newaxis]-1))/(p_dict['friction']+np.sqrt(1+p_dict['friction']**2))
|
|
472
|
+
cluster_instability=np.median(cluster_instability,axis=1)
|
|
473
|
+
|
|
474
|
+
# Computes instability for aux plane (Vavryčuk EQ 16)
|
|
475
|
+
cluster_aux_instability=(aux_tau_shear[:,np.newaxis]-p_dict['friction']*(aux_tau_normal[:,np.newaxis]-1))/(p_dict['friction']+np.sqrt(1+p_dict['friction']**2))
|
|
476
|
+
cluster_aux_instability=np.median(cluster_aux_instability,axis=1)
|
|
477
|
+
|
|
478
|
+
instability[cluster_mech_df.index]=cluster_instability
|
|
479
|
+
aux_instability[cluster_mech_df.index]=cluster_aux_instability
|
|
480
|
+
|
|
481
|
+
return instability, aux_instability
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Functions used for creating synthetic checkerboards.
|
|
3
|
+
|
|
4
|
+
Author: Robert J. Skoumal (rskoumal@usgs.gov)
|
|
5
|
+
'''
|
|
6
|
+
|
|
7
|
+
# External libraries
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
def synth_rake(strike,dip,tau):
|
|
11
|
+
'''
|
|
12
|
+
Computes synthetic rakes given a tau value and catalog of strike,dip.
|
|
13
|
+
'''
|
|
14
|
+
num_events=len(strike)
|
|
15
|
+
strike_radians=np.radians(strike)
|
|
16
|
+
dip_radians=np.radians(dip)
|
|
17
|
+
dip_radians[dip_radians==0]=0.00001
|
|
18
|
+
|
|
19
|
+
# Fault normals
|
|
20
|
+
n=np.zeros((num_events,3))
|
|
21
|
+
n[:,0]=-np.sin(dip_radians)*np.sin(strike_radians)
|
|
22
|
+
n[:,1]=np.sin(dip_radians)*np.cos(strike_radians)
|
|
23
|
+
n[:,2]=-np.cos(dip_radians)
|
|
24
|
+
|
|
25
|
+
term1=np.zeros((num_events,3))
|
|
26
|
+
term2=np.zeros((num_events,3))
|
|
27
|
+
shear_traction=np.zeros((num_events,3))
|
|
28
|
+
shear_trac_1=np.zeros((num_events))
|
|
29
|
+
shear_trac_2=np.zeros((num_events))
|
|
30
|
+
shear_trac_3=np.zeros((num_events))
|
|
31
|
+
|
|
32
|
+
for k in range(0,num_events):
|
|
33
|
+
for i in range(0,3):
|
|
34
|
+
for j in range(0,3):
|
|
35
|
+
term1[k,i]=term1[k,i]+tau[i,j]*n[k,j]
|
|
36
|
+
for l in range(0,3):
|
|
37
|
+
term2[k,i]=term2[k,i]+tau[j,l]*n[k,j]*n[k,l]*n[k,i]
|
|
38
|
+
shear_traction[k,i]=term1[k,i]-term2[k,i]
|
|
39
|
+
shear_traction[(shear_traction==0).all(axis=1),2]=0.0000001
|
|
40
|
+
|
|
41
|
+
# Normalization
|
|
42
|
+
for k in range(0,num_events):
|
|
43
|
+
shear_trac_1[k]=shear_traction[k,0]/np.linalg.norm(shear_traction[k,:],ord=2)
|
|
44
|
+
shear_trac_2[k]=shear_traction[k,1]/np.linalg.norm(shear_traction[k,:],ord=2)
|
|
45
|
+
shear_trac_3[k]=shear_traction[k,2]/np.linalg.norm(shear_traction[k,:],ord=2)
|
|
46
|
+
|
|
47
|
+
tmp=-shear_trac_3/np.sin(dip_radians)
|
|
48
|
+
tmp[tmp>1]=1
|
|
49
|
+
tmp[tmp<-1]=-1
|
|
50
|
+
synth_rake=np.degrees(np.arcsin(tmp))
|
|
51
|
+
|
|
52
|
+
# Determining the quadrant
|
|
53
|
+
cos_rake=shear_trac_1*np.cos(strike_radians)+shear_trac_2*np.sin(strike_radians)
|
|
54
|
+
tmp_flag=cos_rake<0
|
|
55
|
+
synth_rake[tmp_flag]=180-synth_rake[tmp_flag]
|
|
56
|
+
synth_rake[synth_rake<-180]+=360
|
|
57
|
+
synth_rake[synth_rake>180]-=360
|
|
58
|
+
synth_rake=np.round(synth_rake,2)
|
|
59
|
+
|
|
60
|
+
return synth_rake
|
|
61
|
+
|
|
62
|
+
def determine_stress_tensor(shape_ratio,sigma1_az,sigma2_az,theta):
|
|
63
|
+
'''
|
|
64
|
+
Based on code written by Patricia Martínez-Garzón. For more info, see:
|
|
65
|
+
Martínez-Garzón, P., Heidbach, O., & Bohnhoff, M. (2020). Contemporary stress
|
|
66
|
+
and strain field in the Mediterranean from stress inversion of focal mechanisms
|
|
67
|
+
and GPS data. Tectonophysics, 774, 228286.
|
|
68
|
+
'''
|
|
69
|
+
sigma1_az=sigma1_az%360
|
|
70
|
+
sigma2_az=sigma2_az%360
|
|
71
|
+
|
|
72
|
+
theta_radians=np.radians(90-theta)
|
|
73
|
+
sigma1_az_radians=np.radians(sigma1_az)
|
|
74
|
+
sigma2_az_radians=np.radians(sigma2_az)
|
|
75
|
+
|
|
76
|
+
# Calculate deviatoric stress magnitudes
|
|
77
|
+
sigma_1=-1
|
|
78
|
+
sigma_2=sigma_1*(1-2*shape_ratio)/(1+shape_ratio)
|
|
79
|
+
sigma_3=-sigma_1-sigma_2
|
|
80
|
+
|
|
81
|
+
# Normalize
|
|
82
|
+
sigma_dev=sigma_3-sigma_1
|
|
83
|
+
sigma_1=2*sigma_1/sigma_dev
|
|
84
|
+
sigma_2=2*sigma_2/sigma_dev
|
|
85
|
+
sigma_3=2*sigma_3/sigma_dev
|
|
86
|
+
|
|
87
|
+
sigma_1=sigma_1-sigma_3
|
|
88
|
+
sigma_2=sigma_2-sigma_3
|
|
89
|
+
sigma_3=0
|
|
90
|
+
|
|
91
|
+
# Calculate rotated eigenvectors S1, S2, S3 and deviatoric stress tensor (tau)
|
|
92
|
+
sigma_vector_1=np.zeros((3,1))
|
|
93
|
+
sigma_vector_2=np.zeros((3,1))
|
|
94
|
+
sigma_vector_3=np.zeros((3,1))
|
|
95
|
+
|
|
96
|
+
sigma_vector_1[0]=np.sin(theta_radians)*np.cos(sigma1_az_radians)
|
|
97
|
+
sigma_vector_1[1]=np.sin(theta_radians)*np.sin(sigma1_az_radians)
|
|
98
|
+
sigma_vector_1[2]=np.cos(theta_radians)
|
|
99
|
+
|
|
100
|
+
sigma_vector_2[0]=-(np.cos(sigma1_az_radians)*np.cos(sigma2_az_radians)*np.cos(theta_radians)+\
|
|
101
|
+
np.sin(sigma1_az_radians)*np.sin(sigma2_az_radians))
|
|
102
|
+
sigma_vector_2[1]=np.cos(sigma1_az_radians)*np.sin(sigma2_az_radians)-\
|
|
103
|
+
np.cos(sigma2_az_radians)*np.cos(theta_radians)*np.sin(sigma1_az_radians)
|
|
104
|
+
sigma_vector_2[2]=np.cos(sigma2_az_radians)*np.sin(theta_radians)
|
|
105
|
+
|
|
106
|
+
sigma_vector_3[0]=np.cos(sigma1_az_radians)*np.cos(theta_radians)*np.sin(sigma2_az_radians)-\
|
|
107
|
+
np.cos(sigma2_az_radians)*np.sin(sigma1_az_radians)
|
|
108
|
+
sigma_vector_3[1]=np.cos(sigma1_az_radians)*np.cos(sigma2_az_radians)+\
|
|
109
|
+
np.cos(theta_radians)*np.sin(sigma1_az_radians)*np.sin(sigma2_az_radians)
|
|
110
|
+
sigma_vector_3[2]=-np.sin(sigma2_az_radians)*np.sin(theta_radians)
|
|
111
|
+
|
|
112
|
+
tau=sigma_1*sigma_vector_1*sigma_vector_1.T+sigma_2*sigma_vector_2*sigma_vector_2.T+\
|
|
113
|
+
sigma_3*sigma_vector_3*sigma_vector_3.T
|
|
114
|
+
|
|
115
|
+
return tau
|