def calculate_compliance(self): """ Method to calculate compliance and coherence functions from the averaged (daily or station-averaged) noise spectra. Attributes ---------- complyfunc : Dict Container Dictionary for all possible compliance and coherence functions Examples -------- Calculate compliance and coherence functions for a DayNoise object >>> from obstools.atacr import DayNoise >>> from obstools.comply import Comply >>> daynoise = DayNoise('demo') Uploading demo data - March 04, 2012, station 7D.M08A >>> daynoise.QC_daily_spectra() >>> daynoise.average_daily_spectra() >>> daycomply = Comply(objnoise=daynoise, sta=sta) >>> daycomply.calculate_compliance() >>> tfnoise.complyfunc.keys() dict_keys(['ZP', 'ZP-21', 'ZP-H']) Calculate compliance and coherence functions for a StaNoise object >>> from obstools.atacr import StaNoise >>> from obstools.comply import Comply >>> stanoise = StaNoise('demo') Uploading demo data - March 01 to 04, 2012, station 7D.M08A >>> stanoise.QC_sta_spectra() >>> stanoise.average_sta_spectra() >>> stacomply = Comply(objnoise=stanoise, sta=sta) >>> stacomply.calculate_compliance() >>> stacomply.complyfunc.keys() dict_keys(['ZP', 'ZP-21']) """ def wavenumber(omega, H): """ Function to approximate wavenumber from dispersion relation H is depth below the seafloor, in meters omega is a vector of positive angular frequencies Stephen G. Mosher, 2020 """ import numpy.polynomial as poly g = 9.79329 N = len(omega) # Approximations for k when k*H is very large (deep case) or # very small (shallow case) k_deep = omega**2 / g k_shal = omega / np.sqrt(g * H) """ Alternatively, we can use a rational approximation to tanh(x) to solve k for any omega. This approximation gives a quartic equation, we take the positive real roots as the value of k we're interested in. The rational approximation being used is always better than the shallow approximation. However, it's only better than the deep approximation if k*H < 2.96. Therefore, we keep the solutions to k we find, using the rational approximation for k*H < 2.96 and use the deep water approximation to solve for k otherwise. The average error is just under 1% and the maximum error is 2.5%. """ k = np.zeros(len(omega)) for i, om in enumerate(omega): if i == 0: k[i] = 0. else: a0 = -27 * om**2 / g # constant terms a1 = 0. # no linear terms a2 = 27 * H - (9 * om**2 * H**2)/g # quadratic terms a3 = 0. # no cubic terms a4 = H**3 # quartic terms p = poly.Polynomial([a0, a1, a2, a3, a4]) solu = poly.Polynomial.roots(p) positive_roots = solu[solu > 0] real_positive_root = \ positive_roots[positive_roots.imag == 0].real[0] k[i] = real_positive_root # For k*H >= 2.96, prefer the deep approximation above for i, wavenumber in enumerate(k_deep): if wavenumber * H > 2.96: k[i] = k_deep[i] return k # Calculate wavenumber - careful here, elevation is negative k = wavenumber(2.*np.pi*self.f, -1.*self.sta.elevation*1.e3) # Initialize empty dictionary complyfunc = self.ComplyDict() # Cycle through all available transfer functions in the objnoise # object for key, value in self.tf_list.items(): if key == 'ZP': if value: admit_ZP = utils.admittance(self.cZP, self.cPP) compl_ZP = k*admit_ZP coh_ZP = utils.coherence(self.cZP, self.cPP, self.cZZ) complyfunc.add('ZP', [compl_ZP, coh_ZP]) elif key == 'ZP-21': if value: lc1cZ = np.conj(self.c1Z)/self.c11 lc1c2 = np.conj(self.c12)/self.c11 lc1cP = np.conj(self.c1P)/self.c11 coh_12 = utils.coherence(self.c12, self.c11, self.c22) coh_1P = utils.coherence(self.c1P, self.c11, self.cPP) coh_1Z = utils.coherence(self.c1Z, self.c11, self.cZZ) gc2c2_c1 = self.c22*(1. - coh_12) gcPcP_c1 = self.cPP*(1. - coh_1P) gcZcZ_c1 = self.cZZ*(1. - coh_1Z) gc2cZ_c1 = np.conj(self.c2Z) - np.conj(lc1c2*self.c1Z) gcPcZ_c1 = self.cZP - np.conj(lc1cP*self.c1Z) gc2cP_c1 = np.conj(self.c2P) - np.conj(lc1c2*self.c1P) lc2cP_c1 = gc2cP_c1/gc2c2_c1 lc2cZ_c1 = gc2cZ_c1/gc2c2_c1 coh_c2cP_c1 = utils.coherence(gc2cP_c1, gc2c2_c1, gcPcP_c1) coh_c2cZ_c1 = utils.coherence(gc2cZ_c1, gc2c2_c1, gcZcZ_c1) gcPcP_c1c2 = gcPcP_c1*(1. - coh_c2cP_c1) gcPcZ_c1c2 = gcPcZ_c1 - np.conj(lc2cP_c1)*gc2cZ_c1 gcZcZ_c1c2 = gcZcZ_c1*(1. - coh_c2cZ_c1) admit_ZP_21 = utils.admittance( gcPcZ_c1c2, gcPcP_c1c2) compl_ZP_21 = k*admit_ZP_21 coh_ZP_21 = utils.coherence( gcPcZ_c1c2, gcPcP_c1c2, gcZcZ_c1c2) complyfunc.add('ZP-21', [compl_ZP_21, coh_ZP_21]) elif key == 'ZP-H': if value: lcHcP = np.conj(self.cHP)/self.cHH coh_HP = utils.coherence(self.cHP, self.cHH, self.cPP) coh_HZ = utils.coherence(self.cHZ, self.cHH, self.cZZ) gcPcP_cH = self.cPP*(1. - coh_HP) gcZcZ_cH = self.cZZ*(1. - coh_HZ) gcPcZ_cH = self.cZP - np.conj(lcHcP*self.cHZ) admit_ZP_H = utils.admittance(gcPcZ_cH, gcPcP_cH) compl_ZP_H = k*admit_ZP_H coh_ZP_H = utils.coherence(gcPcZ_cH, gcPcP_cH, gcZcZ_cH) complyfunc.add('ZP-H', [compl_ZP_H, coh_ZP_H]) self.complyfunc = complyfunc
def main(args=None): if args is None: # Run Input Parser args = get_cleanspec_arguments() # Load Database # stdb>0.1.3 try: db, stkeys = stdb.io.load_db(fname=args.indb, keys=args.stkeys) # stdb=0.1.3 except: db = stdb.io.load_db(fname=args.indb) # Construct station key loop allkeys = db.keys() sorted(allkeys) # Extract key subset if len(args.stkeys) > 0: stkeys = [] for skey in args.stkeys: stkeys.extend([s for s in allkeys if skey in s]) else: stkeys = db.keys() sorted(stkeys) # Loop over station keys for stkey in list(stkeys): # Extract station information from dictionary sta = db[stkey] # Path where spectra are located specpath = Path('SPECTRA') / stkey if not specpath.is_dir(): raise(Exception("Path to "+str(specpath)+" doesn`t exist - aborting")) # Path where average spectra will be saved avstpath = Path('AVG_STA') / stkey if not avstpath.is_dir(): print("Path to "+str(avstpath)+" doesn`t exist - creating it") avstpath.mkdir(parents=True) # Path where plots will be saved if args.saveplot: plotpath = avstpath / 'PLOTS' if not plotpath.is_dir(): plotpath.mkdir(parents=True) else: plotpath = False # Get catalogue search start time if args.startT is None: tstart = sta.startdate else: tstart = args.startT # Get catalogue search end time if args.endT is None: tend = sta.enddate else: tend = args.endT if tstart > sta.enddate or tend < sta.startdate: continue # Temporary print locations tlocs = sta.location if len(tlocs) == 0: tlocs = [''] for il in range(0, len(tlocs)): if len(tlocs[il]) == 0: tlocs[il] = "--" sta.location = tlocs # Update Display print() print("|===============================================|") print("|===============================================|") print("| {0:>8s} |".format( sta.station)) print("|===============================================|") print("|===============================================|") print("| Station: {0:>2s}.{1:5s} |".format( sta.network, sta.station)) print("| Channel: {0:2s}; Locations: {1:15s} |".format( sta.channel, ",".join(tlocs))) print("| Lon: {0:7.2f}; Lat: {1:6.2f} |".format( sta.longitude, sta.latitude)) print("| Start time: {0:19s} |".format( sta.startdate.strftime("%Y-%m-%d %H:%M:%S"))) print("| End time: {0:19s} |".format( sta.enddate.strftime("%Y-%m-%d %H:%M:%S"))) print("|-----------------------------------------------|") # Filename for output average spectra dstart = str(tstart.year).zfill(4)+'.'+str(tstart.julday).zfill(3)+'-' dend = str(tend.year).zfill(4)+'.'+str(tend.julday).zfill(3)+'.' fileavst = avstpath / (dstart+dend+'avg_sta.pkl') if fileavst.exists(): if not args.ovr: print("* -> file "+str(fileavst)+" exists - continuing") continue # Containers for power and cross spectra coh_all = [] ph_all = [] coh_12_all = [] coh_1Z_all = [] coh_1P_all = [] coh_2Z_all = [] coh_2P_all = [] coh_ZP_all = [] ph_12_all = [] ph_1Z_all = [] ph_1P_all = [] ph_2Z_all = [] ph_2P_all = [] ph_ZP_all = [] ad_12_all = [] ad_1Z_all = [] ad_1P_all = [] ad_2Z_all = [] ad_2P_all = [] ad_ZP_all = [] nwins = [] t1 = tstart # Initialize StaNoise object stanoise = StaNoise() # Loop through each day withing time range while t1 < tend: year = str(t1.year).zfill(4) jday = str(t1.julday).zfill(3) tstamp = year+'.'+jday+'.' filespec = specpath / (tstamp + 'spectra.pkl') # Load file if it exists if filespec.exists(): print() print("*"*60) print('* Calculating noise spectra for key ' + stkey+' and day '+year+'.'+jday) print("* -> file "+str(filespec)+" found - loading") file = open(filespec, 'rb') daynoise = pickle.load(file) file.close() stanoise += daynoise else: t1 += 3600.*24. continue coh_all.append(daynoise.rotation.coh) ph_all.append(daynoise.rotation.ph) # Coherence coh_12_all.append( utils.smooth( utils.coherence( daynoise.cross.c12, daynoise.power.c11, daynoise.power.c22), 50)) coh_1Z_all.append( utils.smooth( utils.coherence( daynoise.cross.c1Z, daynoise.power.c11, daynoise.power.cZZ), 50)) coh_1P_all.append( utils.smooth( utils.coherence( daynoise.cross.c1P, daynoise.power.c11, daynoise.power.cPP), 50)) coh_2Z_all.append( utils.smooth( utils.coherence( daynoise.cross.c2Z, daynoise.power.c22, daynoise.power.cZZ), 50)) coh_2P_all.append( utils.smooth( utils.coherence( daynoise.cross.c2P, daynoise.power.c22, daynoise.power.cPP), 50)) coh_ZP_all.append( utils.smooth( utils.coherence( daynoise.cross.cZP, daynoise.power.cZZ, daynoise.power.cPP), 50)) # Phase try: ph_12_all.append( 180./np.pi*utils.phase(daynoise.cross.c12)) except: ph_12_all.append(None) try: ph_1Z_all.append( 180./np.pi*utils.phase(daynoise.cross.c1Z)) except: ph_1Z_all.append(None) try: ph_1P_all.append( 180./np.pi*utils.phase(daynoise.cross.c1P)) except: ph_1P_all.append(None) try: ph_2Z_all.append( 180./np.pi*utils.phase(daynoise.cross.c2Z)) except: ph_2Z_all.append(None) try: ph_2P_all.append( 180./np.pi*utils.phase(daynoise.cross.c2P)) except: ph_2P_all.append(None) try: ph_ZP_all.append( 180./np.pi*utils.phase(daynoise.cross.cZP)) except: ph_ZP_all.append(None) # Admittance ad_12_all.append(utils.smooth(utils.admittance( daynoise.cross.c12, daynoise.power.c11), 50)) ad_1Z_all.append(utils.smooth(utils.admittance( daynoise.cross.c1Z, daynoise.power.c11), 50)) ad_1P_all.append(utils.smooth(utils.admittance( daynoise.cross.c1P, daynoise.power.c11), 50)) ad_2Z_all.append(utils.smooth(utils.admittance( daynoise.cross.c2Z, daynoise.power.c22), 50)) ad_2P_all.append(utils.smooth(utils.admittance( daynoise.cross.c2P, daynoise.power.c22), 50)) ad_ZP_all.append(utils.smooth(utils.admittance( daynoise.cross.cZP, daynoise.power.cZZ), 50)) t1 += 3600.*24. # Convert to numpy arrays coh_all = np.array(coh_all) ph_all = np.array(ph_all) coh_12_all = np.array(coh_12_all) coh_1Z_all = np.array(coh_1Z_all) coh_1P_all = np.array(coh_1P_all) coh_2Z_all = np.array(coh_2Z_all) coh_2P_all = np.array(coh_2P_all) coh_ZP_all = np.array(coh_ZP_all) ph_12_all = np.array(ph_12_all) ph_1Z_all = np.array(ph_1Z_all) ph_1P_all = np.array(ph_1P_all) ph_2Z_all = np.array(ph_2Z_all) ph_2P_all = np.array(ph_2P_all) ph_ZP_all = np.array(ph_ZP_all) ad_12_all = np.array(ad_12_all) ad_1Z_all = np.array(ad_1Z_all) ad_1P_all = np.array(ad_1P_all) ad_2Z_all = np.array(ad_2Z_all) ad_2P_all = np.array(ad_2P_all) ad_ZP_all = np.array(ad_ZP_all) # Store transfer functions as objects for plotting coh = Cross(coh_12_all, coh_1Z_all, coh_1P_all, coh_2Z_all, coh_2P_all, coh_ZP_all) ph = Cross(ph_12_all, ph_1Z_all, ph_1P_all, ph_2Z_all, ph_2P_all, ph_ZP_all) ad = Cross(ad_12_all, ad_1Z_all, ad_1P_all, ad_2Z_all, ad_2P_all, ad_ZP_all) # Quality control to identify outliers stanoise.QC_sta_spectra(pd=args.pd, tol=args.tol, alpha=args.alpha, fig_QC=args.fig_QC, debug=args.debug, save=plotpath, form=args.form) # Average spectra for good days stanoise.average_sta_spectra( fig_average=args.fig_average, save=plotpath, form=args.form) if args.fig_av_cross: fname = stkey + '.' + 'av_coherence' plot = plotting.fig_av_cross(stanoise.f, coh, stanoise.gooddays, 'Coherence', stanoise.ncomp, key=stkey, lw=0.5) # if plotpath.is_dir(): if plotpath: plot.savefig(str(plotpath / (fname + '.' + args.form)), dpi=300, bbox_inches='tight', format=args.form) else: plot.show() fname = stkey + '.' + 'av_admittance' plot = plotting.fig_av_cross(stanoise.f, ad, stanoise.gooddays, 'Admittance', stanoise.ncomp, key=stkey, lw=0.5) if plotpath: plot.savefig(str(plotpath / (fname + '.' + args.form)), dpi=300, bbox_inches='tight', format=args.form) else: plot.show() fname = stkey + '.' + 'av_phase' plot = plotting.fig_av_cross(stanoise.f, ph, stanoise.gooddays, 'Phase', stanoise.ncomp, key=stkey, marker=',', lw=0) if plotpath: plot.savefig(str(plotpath / (fname + '.' + args.form)), dpi=300, bbox_inches='tight', format=args.form) else: plot.show() if args.fig_coh_ph and stanoise.direc is not None: fname = stkey + '.' + 'coh_ph' plot = plotting.fig_coh_ph(coh_all, ph_all, stanoise.direc) if plotpath: plot.savefig(str(plotpath / (fname + '.' + args.form)), dpi=300, bbox_inches='tight', format=args.form) else: plot.show() # Save to file stanoise.save(fileavst)
def test_StaNoise(): args = test_args.test_get_dailyspec_arguments() stanoise = test_classes.test_stanoise_demo() # Containers for power and cross spectra coh_all = [] ph_all = [] coh_12_all = [] coh_1Z_all = [] coh_1P_all = [] coh_2Z_all = [] coh_2P_all = [] coh_ZP_all = [] ph_12_all = [] ph_1Z_all = [] ph_1P_all = [] ph_2Z_all = [] ph_2P_all = [] ph_ZP_all = [] ad_12_all = [] ad_1Z_all = [] ad_1P_all = [] ad_2Z_all = [] ad_2P_all = [] ad_ZP_all = [] nwins = [] for dn in stanoise.daylist: dn.QC_daily_spectra() dn.average_daily_spectra() coh_all.append(dn.rotation.coh) ph_all.append(dn.rotation.ph) # Coherence coh_12_all.append( utils.smooth( utils.coherence(dn.cross.c12, dn.power.c11, dn.power.c22), 50)) coh_1Z_all.append( utils.smooth( utils.coherence(dn.cross.c1Z, dn.power.c11, dn.power.cZZ), 50)) coh_1P_all.append( utils.smooth( utils.coherence(dn.cross.c1P, dn.power.c11, dn.power.cPP), 50)) coh_2Z_all.append( utils.smooth( utils.coherence(dn.cross.c2Z, dn.power.c22, dn.power.cZZ), 50)) coh_2P_all.append( utils.smooth( utils.coherence(dn.cross.c2P, dn.power.c22, dn.power.cPP), 50)) coh_ZP_all.append( utils.smooth( utils.coherence(dn.cross.cZP, dn.power.cZZ, dn.power.cPP), 50)) # Phase try: ph_12_all.append(180. / np.pi * utils.phase(dn.cross.c12)) except: ph_12_all.append(None) try: ph_1Z_all.append(180. / np.pi * utils.phase(dn.cross.c1Z)) except: ph_1Z_all.append(None) try: ph_1P_all.append(180. / np.pi * utils.phase(dn.cross.c1P)) except: ph_1P_all.append(None) try: ph_2Z_all.append(180. / np.pi * utils.phase(dn.cross.c2Z)) except: ph_2Z_all.append(None) try: ph_2P_all.append(180. / np.pi * utils.phase(dn.cross.c2P)) except: ph_2P_all.append(None) try: ph_ZP_all.append(180. / np.pi * utils.phase(dn.cross.cZP)) except: ph_ZP_all.append(None) # Admittance ad_12_all.append( utils.smooth(utils.admittance(dn.cross.c12, dn.power.c11), 50)) ad_1Z_all.append( utils.smooth(utils.admittance(dn.cross.c1Z, dn.power.c11), 50)) ad_1P_all.append( utils.smooth(utils.admittance(dn.cross.c1P, dn.power.c11), 50)) ad_2Z_all.append( utils.smooth(utils.admittance(dn.cross.c2Z, dn.power.c22), 50)) ad_2P_all.append( utils.smooth(utils.admittance(dn.cross.c2P, dn.power.c22), 50)) ad_ZP_all.append( utils.smooth(utils.admittance(dn.cross.cZP, dn.power.cZZ), 50)) # Convert to numpy arrays coh_all = np.array(coh_all) ph_all = np.array(ph_all) coh_12_all = np.array(coh_12_all) coh_1Z_all = np.array(coh_1Z_all) coh_1P_all = np.array(coh_1P_all) coh_2Z_all = np.array(coh_2Z_all) coh_2P_all = np.array(coh_2P_all) coh_ZP_all = np.array(coh_ZP_all) ph_12_all = np.array(ph_12_all) ph_1Z_all = np.array(ph_1Z_all) ph_1P_all = np.array(ph_1P_all) ph_2Z_all = np.array(ph_2Z_all) ph_2P_all = np.array(ph_2P_all) ph_ZP_all = np.array(ph_ZP_all) ad_12_all = np.array(ad_12_all) ad_1Z_all = np.array(ad_1Z_all) ad_1P_all = np.array(ad_1P_all) ad_2Z_all = np.array(ad_2Z_all) ad_2P_all = np.array(ad_2P_all) ad_ZP_all = np.array(ad_ZP_all) # Store transfer functions as objects for plotting coh = Cross(coh_12_all, coh_1Z_all, coh_1P_all, coh_2Z_all, coh_2P_all, coh_ZP_all) ph = Cross(ph_12_all, ph_1Z_all, ph_1P_all, ph_2Z_all, ph_2P_all, ph_ZP_all) ad = Cross(ad_12_all, ad_1Z_all, ad_1P_all, ad_2Z_all, ad_2P_all, ad_ZP_all) stanoise.QC_sta_spectra(pd=args.pd, tol=args.tol, alpha=args.alpha, fig_QC=True, debug=False, save='tmp', form='png') stanoise.average_sta_spectra(fig_average=True, save='tmp', form='png') plot = plotting.fig_av_cross(stanoise.f, coh, stanoise.gooddays, 'Coherence', stanoise.ncomp, key='7D.M08A', lw=0.5) plot.close() plot = plotting.fig_coh_ph(coh_all, ph_all, stanoise.direc) plot.close() return stanoise