hashpy 1.1.4.post1__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.
hashpy/SKHASH.py ADDED
@@ -0,0 +1,557 @@
1
+ '''
2
+ Python package for earthquake focal mechanism inversions.
3
+
4
+ Author: Robert J. Skoumal (rskoumal@usgs.gov)
5
+
6
+ This project contains Python code to compute focal mechanism solutions using
7
+ first-motion polarities (traditional, consensus, and/or imputed) and S/P ratios
8
+ (traditional and/or consensus).
9
+
10
+ Significant portions of this code are based on the HASH algorithm created by
11
+ Jeanne L. Hardebeck and Peter M. Shearer. If you use this code, please cite the
12
+ appropriate papers.
13
+ Skoumal, R.J., Hardebeck, J.L., & Shearer, P.M. (2024). SKHASH: A Python
14
+ package for computing earthquake focal mechanisms. Seismological Research Letters,
15
+ 95(4), 2519-2526. https://doi.org/10.1785/0220230329
16
+ Hardebeck, J.L., & Shearer, P.M. (2002). A new method for determining
17
+ first-motion focal mechanisms. Bulletin of the Seismological Society of America,
18
+ 92(6), 2264-2276. https://doi.org/10.1785/0120010200
19
+ Hardebeck, J.L., & Shearer, P.M. (2003). Using S/P amplitude ratios to
20
+ constrain the focal mechanisms of small earthquakes. Bulletin of the Seismological
21
+ Society of America, 93(6), 2434-2444. https://doi.org/10.1785/0120020236
22
+ '''
23
+
24
+ # Standard libraries
25
+ import sys
26
+ import time
27
+ import importlib.util
28
+ import multiprocessing
29
+ import argparse
30
+
31
+ # External libraries
32
+ import numpy as np
33
+ import pandas as pd
34
+
35
+ # Local libraries
36
+ import functions.in_pol as in_pol # Reading polarity inputs
37
+ import functions.in_sta as in_sta # Reading station files
38
+ import functions.in_sp as in_sp # Reading S/P ratios
39
+ import functions.in_qc as in_qc # Quality control of user inputs
40
+ import functions.in_other as in_other # Functions for reading other inputs
41
+ import functions.fun as fun # Computing mechanisms
42
+ import functions.out as out # Output functions
43
+ import functions.gridsearch_so as gridsearch_so # For creating fortran gridsearch module
44
+ import functions.compute_mech as compute_mech # For computing mechanism
45
+
46
+ def SKHASH():
47
+ # Superficial version information
48
+ version_string='v1.1'
49
+ version_date='2025-12-17'
50
+
51
+ '''
52
+ Sets default parameter values. If a value is not provided in the control file (or command line) for a variable, the default value is used.
53
+ '''
54
+ p_dict={
55
+ 'input_format':'skhash', # default input file format
56
+ 'input_format_stfile':'', # station list file format
57
+ 'input_format_fpfile':'', # traditional P-polarity file format
58
+ 'input_format_impfile':'', # imputed P-polarity input
59
+ 'input_format_conpfile':'', # consensus P-polarities file format
60
+ 'input_format_dlpfile':'', # deep learning P-polarities file format
61
+ 'input_format_ampfile':'', # amplitude input file format
62
+ 'input_format_relampfile':'', # relative S/P ratio file format
63
+ 'input_format_plfile':'', # polarity reversal format
64
+
65
+ 'controlfile':'',
66
+ 'catfile':'', # earthquake catalog information filename
67
+ 'stfile':'', # station list filename
68
+ 'plfile':'', # station polarity reversal filename
69
+ 'corfile':'', # station correction filename
70
+ 'fpfile':'', # traditional P-polarity input filename
71
+ 'impfile':'', # imputed P-polarity input filename
72
+ 'conpfile':'', # consensus P-polarities from relative measurements input filename
73
+ 'dlpfile':'', # deep learning P-polarities input filename
74
+ 'ampfile':'', # amplitude input filename
75
+ 'relampfile':'', # relative S/P ratios input filename
76
+ 'simulpsfile':'', # filename for SIMULPS azimuth and takeoff angles
77
+ 'outfile1':'default_out1.txt', # focal mechanisms output filename
78
+ 'outfile2':'', # acceptable plane output filename
79
+ 'outfile_pol_agree':'', # record of polarity (dis)agreeement output filename
80
+ 'outfile_sp_agree':'', # record of S/P difference output filename
81
+ 'outfile_pol_info':'', # record of all polarities considered in the mechanisms
82
+ 'outfolder_plots':'', # Folder where simple focal mechanism plots will be created (outfolder_plots/event_id.png). To ignore, leave blank.
83
+
84
+ 'npolmin':8, # mininum number of polarity data (e.g., 8)
85
+ 'nmc':30, # number of trials (e.g., 30)
86
+ 'maxout':500, # the maximum number of acceptable focal mechansims considered (e.g., 500).
87
+ 'ratmin':3.0, # minimum allowed signal-to-noise ratio
88
+ 'badfrac':0.1, # fraction polarities assumed bad
89
+ 'badmin':2.0, # minimum number of polarities per event assumed bad. If badmin > [(sum of the polarity weights) * badfrac], badmin replaces badfrac
90
+ 'qbadfrac':0.3, # assumed noise in amplitude ratios, log10 (e.g. 0.3 for a factor of 2)
91
+ 'qbadmin':2.0, # minimum S/P amplitude misfit allowed per event. If qbadmin > [(sum of the number of S/P measurements) * qbadfrac], qbadmin replaces qbadfrac
92
+ 'min_polarity_weight':0.1, # Any polarities with an abs(weight) < min_polarity_weight will be ignored
93
+ 'delmin':0.0, # minimum allowed source-station distance in km. If delmin<=0, uses all results >= than look_del[0] distance.
94
+ 'delmax':120.0, # maximum allowed source-station distance in km. If delmax<=0, uses all results <= than look_del[1] distance.
95
+ 'azmax':10.0, # maximum allowed source-station azimuth uncertainty in degrees. To use all results regardless of azimuthal uncertainty, set azmax=0.
96
+ 'pmax':0.0, # maximum allowed takeoff ("plungal") uncertainty in degrees. To use all results regardless of takeoff uncertainty, set pmax=0.
97
+ 'cangle':45.0, # angle for computing mechanisms probability
98
+ 'prob_max':0.2, # probability threshold for multiples (e.g., 0.2)
99
+ 'max_agap':90.0, # maximum azimuthal gap
100
+ 'max_pgap':60.0, # maximum "plungal" gap
101
+
102
+ 'vmodel_paths':[], # list of paths to velocity model
103
+ 'write_lookup_table':False, # The lookup table will be saved so that it can be used in future runs. A .npy file extension will be added to the vmodel_paths.
104
+ 'recompute_lookup_table':True, # If a velocity model with an identical filename already exists, it will remake it.
105
+
106
+ 'flip_takeoff':False, # If True, takeoff angles read/written will correspond to [180: upgoing and 0: downgoing]. If False, it will follow HASH notation [0: upgoing, 180: downgoing].
107
+
108
+ 'require_temporal_match':False, # Requires the reported earthquake to occur between the station metadata start-end time.
109
+ 'require_network_match':True, # Requires the reported pick network to match the metadata
110
+ 'require_station_match':True, # Requires the reported pick station to match the metadata
111
+ 'require_channel_match':True, # Requires the reported pick channel to match the metadata
112
+ 'require_location_match':True, # Requires the reported pick location to match the metadata
113
+ 'allow_duplicate_stations':False, # Allows an event_id to have multiple polarities and S/P ratios from the same receiver
114
+ 'remove_duplicate_stations':False, # If an event_id has multiple polarities or S/P ratios from the same receiver, removes the duplicates
115
+ 'iterative_avg':False, # If True, it will iteratively compute the average mech by removing the solution furthest from the avg following HASH.
116
+
117
+ 'min_quality_report':'', # Only mech qualities of this or better will be used to reported. To consider all accepted solutions, leave blank.
118
+
119
+ 'overwrite_output_file':True, # If True, SKHASH will overwrite any existing output files.
120
+ 'perturb_epicentral_location':False, # If True, it will randomly perturb the horizontal location of the earthquakes
121
+
122
+ 'ignore_missing_metadata':False, # If True, any measurements with missing metadata will be ignored. If False, an error will be raised.
123
+
124
+ 'use_fortran':False, # If True, it will attempt to load the Fortran subroutine. If it cannot load or is False, the Python routine will be used.
125
+ 'num_cpus':1, # Number of cores to run things in parallel. Set to 0 to use all available, or 1 to run in serial.
126
+
127
+ 'dang':5, # minimum grid spacing (degrees).
128
+ 'nx0':101, # maximum source-station distance bins for look-up tables
129
+ 'nd0':14, # maximum source depth bins for look-up tables
130
+ 'look_dep':[0,39,3], # minimum source depth, maximum, and interval for the lookup table
131
+ 'look_del':[0,200,2], # minimum source-station distance, maximum, and interval for the lookup table
132
+ 'allow_hypocenters_outside_table':False, # If a hypocenter is outside of the lookup table range, the min/max lookup value is used. If False, an error will be produced.
133
+ 'nump':9000, # number of rays traced
134
+
135
+ 'min_sp':0.0005,# If any user-provided S/P ratios are less than min_sp after applying station corrections, those values are replaced with min_sp. To ignore, use a value <= 0.
136
+ 'max_sp':2000.0, # If any user-provided S/P ratios are greater than max_sp after applying station corrections, those values are replaced with max_sp. To ignore, use a value <= 0.
137
+ 'min_amp':0.0005, # minimum amplitude [0-1] considered when calculating expected P and S amplitudes. Values < than this are treated as 0. To ignore, use a value <= 0.
138
+
139
+ 'epicenter_degree_precision':6, # Number of decimal places to round lat, lon epicenter coordinates.
140
+ 'output_angle_precision':1, # Number of decimal places to output for angles (e.g., strike, dip, rake, takeoff)
141
+ 'output_quality_precision':1, # Number of decimal places to output for mech qualities (e.g., prob, mfrac, mavg, stdr)
142
+ 'output_km_distance_precision':3, # Number of decimal places to output for source-receiver distances (reported in km)
143
+ 'output_vector_precision':4, # Number of decimal places to output for normal vectors
144
+ 'default_vert_uncert_km':1.0, # If the vertical uncertainty for an earthquake is missing or negative, this value is used instead when perturbing the source depth.
145
+ 'default_horz_uncert_km':1.0, # If the horizontal uncertainty for an earthquake is missing or negative, this value is used instead when perturbing the source hypocenter.
146
+ 'default_takeoff_uncert':0.0, # If the precomputed takeoff uncertainty is missing or negative, this value is used instead when perturbing takeoffs
147
+ 'default_azimuth_uncert':0.0, # If the precomputed azimuth uncertainty is missing or negative, this value is used instead when perturbing azimuths
148
+
149
+ 'plot_station_names':False, # If True, station names will be included in the beachball plot
150
+ 'plot_acceptable_solutions':False, # If True, all acceptable solutions will be plotted in the beachball plot
151
+ 'plot_mult_solutions':False, # If True and multiple solutions are found, all solutions will be plotted in the beachball plot
152
+
153
+ # Warning: only change the following values if you know what you're doing! :)
154
+ 'compute_takeoff_azimuth':True, # Used to determine if takeoff and source-receiver azimuths need to be computed
155
+ 'npick0':15000, # maximum number of picks per event. Used by Fortran gridsearch. If this is changed, the value in gridsearch.f must be changed and recompiled.
156
+ 'merge_on':[] # Controls how to merge metadata with observations. List that includes ('network', 'station', 'location', 'channel')
157
+ }
158
+
159
+ # Quality codes should be listed from best quality (most restrictive) to worst quality (least restrictive)
160
+ qual_criteria_dict={
161
+ 'qual_letter':np.asarray([ 'A', 'B', 'C', 'D']),
162
+ 'probs':np.asarray( [ 0.8, 0.6, 0.5, 0 ]),
163
+ 'var_avg':np.asarray( [ 25, 35, 45, np.inf ]),
164
+ 'mfrac':np.asarray( [0.15, 0.20, 0.30, np.inf ]),
165
+ 'stdr':np.asarray( [ 0.5, 0.4, 0.3, 0 ]),
166
+ # If the following conditions are not met, the lowest quality letter is given.
167
+ 'azimuthal_gap':90, # max azimuthal gap
168
+ 'takeoff_gap':60, # max takeoff gap
169
+ 'num_p_pol':8, # minimum number of p polarities
170
+ }
171
+
172
+ '''
173
+ Reads command line arguments.
174
+ Note that if you also use a control file, the control file variable will overwrite command line arguments.
175
+ '''
176
+ parser = argparse.ArgumentParser()
177
+ parser.add_argument('controlfile',nargs='?')
178
+ for p_dict_var in p_dict.keys():
179
+ parser.add_argument('--'+p_dict_var)
180
+ args = vars(parser.parse_args())
181
+ for p_dict_var in p_dict.keys():
182
+ if args[p_dict_var] is not None:
183
+ dtype=type(p_dict[p_dict_var])
184
+ if dtype==bool:
185
+ if args[p_dict_var].lower() in ['true','1']:
186
+ p_dict[p_dict_var]=True
187
+ elif args[p_dict_var].lower() in ['false','0']:
188
+ p_dict[p_dict_var]=False
189
+ else:
190
+ raise ValueError(('Expected a boolean for the command-line declared variable \'{}\''+\
191
+ 'The provided value ({}) is not a boolean.').format(p_dict_var,args[p_dict_var]))
192
+ else:
193
+ p_dict[p_dict_var]=dtype(args[p_dict_var])
194
+
195
+ print('========================\nSKHASH {} ({})\n========================'.format(version_string,version_date))
196
+ total_runtime_start=time.time()
197
+
198
+ '''
199
+ Checks Python version.
200
+ '''
201
+ if not((sys.version_info.major==3) & (sys.version_info.minor>=8)):
202
+ print('***WARNING: SKHASH has only been tested on Python 3.8+. Refer to the manual for supported versions.')
203
+
204
+ '''
205
+ Reads the user-created control file, overwritting the default and commonand line parameter values.
206
+ '''
207
+ if p_dict['controlfile']:
208
+ print('Control file: {}'.format(p_dict['controlfile']))
209
+ p_dict=in_other.read_control_file(p_dict['controlfile'],p_dict)
210
+
211
+ '''
212
+ Loads/creates the fortran extension module
213
+ '''
214
+ if p_dict['use_fortran']:
215
+ gridsearch_spec = importlib.util.find_spec("functions.gridsearch")
216
+ if (gridsearch_spec is not None):
217
+ import functions.gridsearch as gridsearch
218
+ print('Fortran subroutine module successfully loaded.')
219
+ else:
220
+ status=gridsearch_so.create()
221
+ if status:
222
+ import functions.gridsearch as gridsearch
223
+ else:
224
+ p_dict['use_fortran']=False
225
+ print('Unable to load Fortran subroutine. Falling back to using the Python gridsearch routine.\n')
226
+
227
+ '''
228
+ Performs various checks to ensure that the user-defined parameters are appropriate
229
+ '''
230
+ p_dict=in_qc.check_input_params(p_dict,qual_criteria_dict)
231
+
232
+ '''
233
+ Reads P-wave first motion polarity file(s)
234
+ '''
235
+ pol_df=[]
236
+ cat_df=[]
237
+ if p_dict['fpfile']:
238
+ tmp_cat_df,tmp_pol_df=in_pol.read_polarity_file(p_dict['fpfile'],p_dict['input_format_fpfile'],p_dict['merge_on'])
239
+ tmp_pol_df['source']='trad_p'
240
+ if len(tmp_pol_df):
241
+ pol_df.append(tmp_pol_df)
242
+ if len(tmp_cat_df):
243
+ cat_df.append(tmp_cat_df)
244
+ if p_dict['impfile']:
245
+ tmp_cat_df,tmp_pol_df=in_pol.read_polarity_file(p_dict['impfile'],p_dict['input_format_impfile'],p_dict['merge_on'])
246
+ tmp_pol_df['source']='imp_p'
247
+ if len(tmp_pol_df):
248
+ pol_df.append(tmp_pol_df)
249
+ if len(tmp_cat_df):
250
+ cat_df.append(tmp_cat_df)
251
+ if p_dict['conpfile']:
252
+ tmp_cat_df,tmp_pol_df=in_pol.read_polarity_file(p_dict['conpfile'],p_dict['input_format_conpfile'],p_dict['merge_on'])
253
+ tmp_pol_df['source']='con_p'
254
+ if len(tmp_pol_df):
255
+ pol_df.append(tmp_pol_df)
256
+ if len(tmp_cat_df):
257
+ cat_df.append(tmp_cat_df)
258
+ if p_dict['dlpfile']:
259
+ tmp_cat_df,tmp_pol_df=in_pol.read_polarity_file(p_dict['dlpfile'],p_dict['input_format_dlpfile'],p_dict['merge_on'])
260
+ tmp_pol_df['source']='dl_p'
261
+ if len(tmp_pol_df):
262
+ pol_df.append(tmp_pol_df)
263
+ if len(tmp_cat_df):
264
+ cat_df.append(tmp_cat_df)
265
+ if len(pol_df):
266
+ pol_df=pd.concat(pol_df).reset_index(drop=True)
267
+ else:
268
+ pol_df=pd.DataFrame()
269
+ print('*WARNING: No polarity measurements provided.')
270
+ if len(cat_df):
271
+ cat_df=pd.concat(cat_df).drop_duplicates(subset='event_id').reset_index(drop=True)
272
+
273
+ # Adds SIMULPS source-receiver distances, takeoff, and azimuths
274
+ if p_dict['simulpsfile']:
275
+ pol_df=in_other.read_simulps(p_dict['simulpsfile'],pol_df)
276
+
277
+ '''
278
+ Reads S/P amplitude file, calculates S/P ratios, discards S/P ratios below the noise threshold, and applies station corrections.
279
+ '''
280
+ if p_dict['ampfile']:
281
+ spamp_df,p_dict['ampfile']=in_sp.read_amp_corr_files(p_dict['ampfile'],p_dict)
282
+ spamp_df['source']='trad_sp'
283
+
284
+ # Concats polarity and S/P data into a single dataframe
285
+ pol_df=pd.concat([pol_df,spamp_df]).reset_index(drop=True)
286
+
287
+ if p_dict['relampfile']:
288
+ spamp_df,p_dict=in_sp.read_amp_corr_files(p_dict['relampfile'],p_dict)
289
+ spamp_df['source']='rel_sp'
290
+
291
+ # Concats polarity and consensus S/P data into a single dataframe
292
+ pol_df=pd.concat([pol_df,spamp_df]).reset_index(drop=True)
293
+
294
+ # Looks for duplicate polarity and S/P measurements
295
+ if not(p_dict['allow_duplicate_stations']):
296
+ if len(pol_df):
297
+ if 'sp_ratio' in pol_df.columns:
298
+ dup_df=pd.concat([pol_df[['event_id','sta_code']],pd.isnull(pol_df[['p_polarity','sp_ratio']])],axis=1)
299
+ else:
300
+ dup_df=pd.concat([pol_df[['event_id','sta_code']],pd.isnull(pol_df[['p_polarity']])],axis=1)
301
+ duplicate_polarity_index=pol_df[dup_df.duplicated()].index
302
+ if not(duplicate_polarity_index.empty):
303
+ if p_dict['remove_duplicate_stations']:
304
+ print('Removing duplicate information for {} measurements. Example issues:\n{}'.
305
+ format(len(duplicate_polarity_index),pol_df.loc[duplicate_polarity_index,['event_id','sta_code']].head(10).to_string()))
306
+ pol_df=pol_df.drop(duplicate_polarity_index).reset_index(drop=True)
307
+ else:
308
+ raise ValueError('Duplicate information for {} measurements. Example issues:\n{}'.
309
+ format(len(duplicate_polarity_index),pol_df.loc[duplicate_polarity_index,['event_id','sta_code']].head(10).to_string()))
310
+
311
+ if len(pol_df)==0:
312
+ print('No polarity information provided. Exiting.')
313
+ quit()
314
+
315
+ '''
316
+ Reads earthquake catalog
317
+ '''
318
+ if p_dict['catfile']:
319
+ cat_df=in_other.read_catalog_file(p_dict)
320
+
321
+ # Adds event times, locations, and uncertainties to polarity information
322
+ if len(cat_df):
323
+ if not('event_id2' in pol_df.columns):
324
+ pol_df=pol_df.merge(cat_df,on='event_id',how='left')
325
+ else: # composite mechs
326
+ pol_df=pol_df.merge(cat_df.rename(columns={'event_id':'event_id2'}),on='event_id2',how='left')
327
+
328
+ '''
329
+ Quality controls the datasets
330
+ '''
331
+ # Quality control of catalog dataset
332
+ cat_df=in_qc.check_cat_df(cat_df,pol_df,p_dict)
333
+
334
+ # Quality control of polarity dataset
335
+ pol_df=in_qc.check_pol_df(pol_df,cat_df,p_dict)
336
+
337
+ '''
338
+ Checks that earthquake locations will be contained in the model
339
+ '''
340
+ if p_dict['compute_takeoff_azimuth']:
341
+ pol_df,p_dict=in_qc.check_model_eq(cat_df,pol_df,p_dict)
342
+
343
+ '''
344
+ Reads station polarity reversals and applies them to the polarity measurements
345
+ '''
346
+ if p_dict['plfile']:
347
+ pol_reverse_df=in_sta.read_reverse_file(p_dict)
348
+ pol_df=in_sta.reverse_polarities(pol_df,pol_reverse_df,p_dict)
349
+ pol_df=pol_df.drop(pol_df.filter(['station']),axis=1)
350
+
351
+ '''
352
+ Reads station metadata file and appends the locations to the polarities
353
+ '''
354
+ if p_dict['stfile']:
355
+ station_df=in_sta.read_station_file(pol_df,p_dict)
356
+ pol_df=in_sta.apply_station_locations(pol_df,station_df,p_dict)
357
+
358
+ # Drops columns that are no longer needed
359
+ if 'origin_DateTime' in pol_df:
360
+ pol_df=pol_df.drop(columns=['origin_DateTime'])
361
+
362
+ '''
363
+ Dropping any measurements with source-receiver distances > delmax
364
+ '''
365
+ pol_df['sr_dist_km']=pol_df['sr_dist_km'].round(p_dict['output_km_distance_precision'])
366
+ if (p_dict['delmax']>0) | (p_dict['delmin']>0):
367
+ drop_flag=((pol_df['sr_dist_km']>p_dict['delmax']) | (pol_df['sr_dist_km']<p_dict['delmin']))
368
+ if np.any(drop_flag):
369
+ if p_dict['delmin']>0:
370
+ print('Discarded {} of the {} polarity measurements with source-receiver distances <{} or >{} km'.
371
+ format(np.sum(drop_flag),len(pol_df),p_dict['delmin'],p_dict['delmax']))
372
+ else:
373
+ print('Discarded {} of the {} polarity measurements with source-receiver distances >{} km'.
374
+ format(np.sum(drop_flag),len(pol_df),p_dict['delmax']))
375
+ pol_df=pol_df.drop(pol_df[drop_flag].index).reset_index(drop=True)
376
+
377
+ '''
378
+ Calculates maximum possible source-receiver azimuth variation, dropping any polarities > azmax
379
+ '''
380
+ if p_dict['azmax']>0:
381
+ if not('azimuth_uncertainty' in pol_df.columns):
382
+ sr_unc_flag=pol_df['horz_uncert_km']<=pol_df['sr_dist_km']
383
+
384
+ pol_df['azimuth_uncertainty']=(90-np.rad2deg(np.arccos(pol_df['horz_uncert_km']/pol_df['sr_dist_km'].values)))*2
385
+
386
+ # If the horizontal uncertainty is greater than the source-receiver distance, any azimuth is possible.
387
+ tmp_index=pol_df[~sr_unc_flag].index
388
+ pol_df.loc[tmp_index,'azimuth_uncertainty']=360
389
+
390
+ # Drop polarities that have a source-receiver azimuth variation > azmax
391
+ tmp_index=pol_df[pol_df['azimuth_uncertainty'] > p_dict['azmax']].index
392
+ if not(tmp_index.empty):
393
+ pol_df.drop(tmp_index, inplace=True)
394
+ print('Discarded {} polarity measurement(s) with source-receiver azimuth uncertainties >{} deg'.format(len(tmp_index),p_dict['azmax']))
395
+
396
+ pol_df['azimuth_uncertainty']=pol_df['azimuth_uncertainty'].round(p_dict['output_angle_precision'])
397
+ pol_df=pol_df.reset_index(drop=True)
398
+
399
+ '''
400
+ If a station file is given but takeoff and azimuths are already provided, ignore the station file.
401
+ '''
402
+ if p_dict['stfile']:
403
+ if (~(pd.isnull(pol_df[['takeoff','azimuth']]))).any(axis=None):
404
+ print('Ignoring the stfile ({}) because takeoff and azimuths are already provided.'.format(p_dict['stfile']))
405
+ p_dict['stfile']=''
406
+
407
+ '''
408
+ If using precomputed takeoff/azimuths, discards polarities with takeoffs with large uncertainties.
409
+ If computing takeoff/azimuths, this is handled later in compute_mech().
410
+ '''
411
+ # Discards any measurements with a takeoff uncertainty > pmax
412
+ if not(p_dict['stfile']):
413
+ if p_dict['pmax']>0:
414
+ if 'takeoff_uncertainty' in pol_df.columns:
415
+ drop_index=pol_df[(pol_df['takeoff_uncertainty']>p_dict['pmax'])].index
416
+ if len(drop_index)>0:
417
+ print('Discarded {} polarity measurement(s) with takeoff uncertainties >{} deg'.format(len(drop_index),p_dict['pmax']))
418
+ pol_df=pol_df.drop(drop_index).reset_index(drop=True)
419
+
420
+ '''
421
+ Discards earthquakes with fewer than npolmin P-polarities
422
+ '''
423
+ if p_dict['npolmin']>0:
424
+ tmp_df=pol_df.groupby(by='event_id').agg({'p_polarity':'count'})
425
+ discard_event_id=tmp_df[tmp_df['p_polarity']<p_dict['npolmin']].index
426
+ if not(discard_event_id.empty):
427
+ pol_df=pol_df.drop(pol_df[pol_df['event_id'].isin(discard_event_id)].index).reset_index(drop=True)
428
+ print('Discarded {} earthquakes with fewer than {} polarities.'.format(len(discard_event_id),p_dict['npolmin']))
429
+
430
+ '''
431
+ Removes cataloged earthquakes that have no selected measurements
432
+ '''
433
+ if len(cat_df):
434
+ cat_consider_flag=~(cat_df['event_id'].isin(pol_df['event_id']))
435
+ if cat_consider_flag.any():
436
+ cat_df=cat_df.drop(cat_df[cat_consider_flag].index).reset_index(drop=True)
437
+
438
+ '''
439
+ Reads the velocity model files and creates (or loads) the lookup tables.
440
+ '''
441
+ if p_dict['compute_takeoff_azimuth']:
442
+ lookup_dict=fun.create_lookup_table(p_dict)
443
+ else:
444
+ lookup_dict={'deptab':[],'delttab':[],'table':[]}
445
+
446
+ '''
447
+ Sets up array with direction cosines for all coordinate transformations
448
+ '''
449
+ if p_dict['use_fortran']:
450
+ dir_cos_dict={}
451
+ else:
452
+ dir_cos_dict=fun.dir_cos_setup(p_dict)
453
+
454
+ '''
455
+ Groups polarities and S/P ratios by event_id
456
+ '''
457
+ group_pol_df=pol_df.groupby(by='event_id')
458
+ event_ids=list(group_pol_df.groups.keys())
459
+
460
+ if p_dict['outfile_pol_agree']:
461
+ pol_df['pol_agreement']=0
462
+ if p_dict['outfile_sp_agree']:
463
+ pol_df['sp_diff']=-999.
464
+
465
+ # Creates outfile1/outfile2 and adds header lines to them.
466
+ if p_dict['outfile1']:
467
+ out.create_outfile1(p_dict['outfile1'],cat_df,pol_df)
468
+ if p_dict['outfile2']:
469
+ out.create_outfile2(p_dict['outfile2'])
470
+
471
+ if not(event_ids):
472
+ print('No mechanisms to compute. Exiting.')
473
+ quit()
474
+
475
+ '''
476
+ Computes the focal mechanisms
477
+ '''
478
+ mech_runtime_start=time.time()
479
+ num_events=len(event_ids)
480
+ if p_dict['num_cpus']==1: # Run in serial
481
+ print('Computing mechanisms in serial...')
482
+ for event_x,event_id in enumerate(event_ids):
483
+ mech_dict=compute_mech.compute_mech(event_x,num_events,event_id,group_pol_df.get_group(event_id),
484
+ p_dict,lookup_dict,qual_criteria_dict,cat_df,dir_cos_dict)
485
+ if mech_dict['mech_qual']:
486
+ pol_df.loc[mech_dict['event_index'],'mech_quality']=mech_dict['mech_qual']
487
+ if p_dict['outfile_pol_agree']:
488
+ pol_df.loc[mech_dict['event_index'],'pol_agreement']=mech_dict['pol_agreement_out']
489
+ if p_dict['outfile_sp_agree']:
490
+ pol_df.loc[mech_dict['event_index'],'sp_diff']=mech_dict['sp_diff_out']
491
+ if (p_dict['stfile']) and (p_dict['outfile_pol_info']):
492
+ pol_df.loc[mech_dict['event_index'],'takeoff']=mech_dict['takeoff']
493
+ pol_df.loc[mech_dict['event_index'],'azimuth']=mech_dict['sr_az']
494
+ pol_df.loc[mech_dict['event_index'],'takeoff_uncertainty']=mech_dict['takeoff_uncertainty']
495
+ pol_df.loc[mech_dict['event_index'],'azimuth_uncertainty']=mech_dict['azimuth_uncertainty']
496
+ else: # Run in parallel
497
+ print('Computing mechanisms in parallel...')
498
+ pool=multiprocessing.Pool(processes=p_dict['num_cpus'])
499
+
500
+ async_results=[]
501
+ for event_x,event_id in enumerate(event_ids):
502
+ try:
503
+ event_pol_df=group_pol_df.get_group(event_id)
504
+ except:
505
+ print('Error getting parallel result for event_id: {}'.format(event_id))
506
+ continue
507
+ async_results.append(pool.apply_async(compute_mech.compute_mech,
508
+ args=(event_x,num_events,event_id,event_pol_df,p_dict,lookup_dict,qual_criteria_dict,cat_df,dir_cos_dict)))
509
+ pool.close()
510
+ pool.join()
511
+
512
+ if any([p_dict['outfile_pol_agree'],p_dict['outfile_sp_agree'],p_dict['outfile_pol_info']]):
513
+ for result in async_results:
514
+ mech_dict=result.get()
515
+ if mech_dict['mech_qual']:
516
+ pol_df.loc[mech_dict['event_index'],'mech_quality']=mech_dict['mech_qual']
517
+ if p_dict['outfile_pol_agree']:
518
+ pol_df.loc[mech_dict['event_index'],'pol_agreement']=mech_dict['pol_agreement_out']
519
+ if p_dict['outfile_sp_agree']:
520
+ pol_df.loc[mech_dict['event_index'],'sp_diff']=mech_dict['sp_diff_out']
521
+ if (p_dict['stfile']) and (p_dict['outfile_pol_info']):
522
+ pol_df.loc[mech_dict['event_index'],'takeoff']=mech_dict['takeoff']
523
+ pol_df.loc[mech_dict['event_index'],'azimuth']=mech_dict['sr_az']
524
+ pol_df.loc[mech_dict['event_index'],'takeoff_uncertainty']=mech_dict['takeoff_uncertainty']
525
+ pol_df.loc[mech_dict['event_index'],'azimuth_uncertainty']=mech_dict['azimuth_uncertainty']
526
+
527
+ print('Mech computation runtime: {:.2f} sec'.format(time.time()-mech_runtime_start), flush=True)
528
+
529
+ if p_dict['flip_takeoff']:
530
+ pol_df['takeoff']=180-pol_df['takeoff']
531
+
532
+ '''
533
+ Determines the polarity agreements at the different stations and writes it to file
534
+ '''
535
+ if p_dict['outfile_pol_agree']:
536
+ out.pol_agree(pol_df,p_dict)
537
+
538
+ '''
539
+ Determines the S/P agreements at the different stations and writes it to file
540
+ '''
541
+ if p_dict['outfile_sp_agree']:
542
+ out.sp_agree(pol_df,p_dict)
543
+
544
+ '''
545
+ Creates output file with lots of information about the polarity measurements used to compute the focal mechanisms
546
+ '''
547
+ if p_dict['outfile_pol_info']:
548
+ out.pol_info(pol_df,p_dict)
549
+
550
+ print('Total runtime: {:.2f} sec'.format(time.time()-total_runtime_start), flush=True)
551
+ sys.exit()
552
+
553
+ def main():
554
+ SKHASH()
555
+
556
+ if __name__ == '__main__':
557
+ main()
hashpy/__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__)))