def smPSA(data, samp_rate): """ ShakeMap pseudo-spectral parameters Compute 5% damped PSA at 0.3, 1.0, and 3.0 seconds. Data must be an acceleration Trace :type data: :class:`obspy.trace` :param data: Data in acceleration to convolve with pendulum at freq. :type delta: float :param delta: sample rate (samples per sec) :rtype: (float, float, float) :return: PSA03, PSA10, PSA30 """ D = 0.05 # 5% damping out = [] periods = [0.3, 1.0, 3.0] for T in periods: freq = 1.0 / T omega = (2 * 3.14159 * freq)**2 paz_sa = cornFreq2Paz(freq, damp=D) paz_sa['sensitivity'] = omega paz_sa['zeros'] = [] dd = seisSim(data.data, samp_rate, paz_remove=None, paz_simulate=paz_sa, taper=True, simulate_sensitivity=True, taper_fraction=0.05) if abs(max(dd)) >= abs(min(dd)): psa = abs(max(dd)) else: psa = abs(min(dd)) out.append(psa) return out
def smPSA(data, samp_rate): """ ShakeMap pseudo-spectral parameters Compute 5% damped PSA at 0.3, 1.0, and 3.0 seconds. Data must be an acceleration Trace :type data: :class:`obspy.trace` :param data: Data in acceleration to convolve with pendulum at freq. :type delta: float :param delta: sample rate (samples per sec) :rtype: (float, float, float) :return: PSA03, PSA10, PSA30 """ D = 0.05 # 5% damping out = [] periods = [0.3, 1.0, 3.0] for T in periods: freq = 1.0 / T omega = (2 * 3.14159 * freq) ** 2 paz_sa = cornFreq2Paz(freq, damp=D) paz_sa['sensitivity'] = omega paz_sa['zeros'] = [] dd = seisSim(data.data, samp_rate, paz_remove=None, paz_simulate=paz_sa, taper=True, simulate_sensitivity=True, taper_fraction=0.05) if abs(max(dd)) >= abs(min(dd)): psa = abs(max(dd)) else: psa = abs(min(dd)) out.append(psa) return out
def pgm(data, delta, freq, damp=0.1): """ Peak ground motion parameters Compute the maximal displacement, velocity, acceleration and the peak ground acceleration at a certain frequency (standard frequencies for ShakeMaps are 0.3/1.0/3.0 Hz). Data must be displacement :type data: :class:`~numpy.ndarray` :param data: Data in displacement to convolve with pendulum at freq. :type delta: float :param delta: Sampling interval :type freq: float :param freq: Frequency in Hz. :type damp: float :param damp: damping factor. Default is set to 0.1 :rtype: (float, float, float, float) :return: Peak Ground Acceleration, maximal displacement, velocity, acceleration """ data = data.copy() # Displacement if abs(max(data)) >= abs(min(data)): m_dis = abs(max(data)) else: m_dis = abs(min(data)) # Velocity data = np.gradient(data, delta) if abs(max(data)) >= abs(min(data)): m_vel = abs(max(data)) else: m_vel = abs(min(data)) # Acceleration data = np.gradient(data, delta) if abs(max(data)) >= abs(min(data)): m_acc = abs(max(data)) else: m_acc = abs(min(data)) samp_rate = 1.0 / delta T = freq * 1.0 D = damp omega = (2 * 3.14159 * T) ** 2 paz_sa = cornFreq2Paz(T, damp=D) paz_sa["sensitivity"] = omega paz_sa["zeros"] = [] data = seisSim( data, samp_rate, paz_remove=None, paz_simulate=paz_sa, taper=True, simulate_sensitivity=True, taper_fraction=0.05, ) if abs(max(data)) >= abs(min(data)): pga = abs(max(data)) else: pga = abs(min(data)) return (pga, m_dis, m_vel, m_acc)
def pgm(data, delta, freq, damp=0.1): """ Peak ground motion parameters Compute the maximal displacement, velocity, acceleration and the peak ground acceleration at a certain frequency (standard frequencies for ShakeMaps are 0.3/1.0/3.0 Hz). Data must be displacement :type data: :class:`~numpy.ndarray` :param data: Data in dispalcement to convolve with pendulum at freq. :type delta: float :param delta: Sampling interval :type freq: float :param freq: Frequency in Hz. :type damp: float :param damp: damping factor. Default is set to 0.1 :rtype: (float, float, float, float) :return: Peak Ground Acceleration, maximal displacement, velocity, acceleration """ data = data.copy() # Displacement if abs(max(data)) >= abs(min(data)): m_dis = abs(max(data)) else: m_dis = abs(min(data)) # Velocity data = np.gradient(data, delta) if abs(max(data)) >= abs(min(data)): m_vel = abs(max(data)) else: m_vel = abs(min(data)) # Acceleration data = np.gradient(data, delta) if abs(max(data)) >= abs(min(data)): m_acc = abs(max(data)) else: m_acc = abs(min(data)) samp_rate = 1.0 / delta T = freq * 1.0 D = damp omega = (2 * 3.14159 * T) ** 2 paz_sa = cornFreq2Paz(T, damp=D) paz_sa['sensitivity'] = omega paz_sa['zeros'] = [] data = seisSim(data, samp_rate, paz_remove=None, paz_simulate=paz_sa, taper=True, simulate_sensitivity=True, taper_fraction=0.05) if abs(max(data)) >= abs(min(data)): pga = abs(max(data)) else: pga = abs(min(data)) return (pga, m_dis, m_vel, m_acc)
def find_pole_zero(freq_msu,amp_msu,seismometer,msu_freep,msu_damp,nsearch,coarse_search,fine_search,nloops,ngrids,lmult,hmult): # ngrids is the number of grid points to be searched for each parameter, per each "percentage" loop # At nsearch = 3, grid search on amplitude, damping ratio, and free period. nfreqs = ngrids if ( nsearch < 3 ): # Set flags. Free period is constrained at nsearch = 2 nfreqs = 1 ndamps = ngrids if ( nsearch < 2 ): # Set flags. Free period & Damping ratio is constrained at nsearch = 1 ndamps = 1 nscales = ngrids if ( nsearch < 1 ): # set flags. Free period, Damping Ratio, and amplitude are all constrained. nscales = 1 # search the grids nloops times, first between coarse_search percent smaller than starting params # and coarse_search percent larger than starting params, then, eventually search only within fine_search percent # of the best params found on each previous loop search search_range = np.linspace(coarse_search, fine_search, nloops) # freq_msu is array holding frequency values where MSU made amp measurements # amp_msu is array holding amplitude values measured by MSU # note that msu_freep is the MSU estimated freee period in seconds # now find average amplitude from MSU measurements where freq > 1 Hz # # edit: Change the average amplitude calculation to use frequencies greater than # 3x the free period frequency but less than 8x the free-period frequency. # This program calculates only two poles & zeros: Response should be flat within # this passband and avoids any weird issues at higher frequencies and keeps # calculation off the knee of the response curve in cases of high or low damping # ratios # - drb 08/28/2014 count = 0 amp_average = 0. # lmult = lmult/msu_freep # make it a multiplier of freeperiod in Hz (lowest) # hmult = hmult/msu_freep # (Highest) for i, freq in enumerate(freq_msu): if ( freq > lmult) and (freq < hmult): # Set frequency discriminator to msu_freep * 2 to get off the # knee of the curve for SP instruments - drb amp_average = amp_average + amp_msu[i] count += 1 amp_average = amp_average / float(count) # set preliminary "best parameters" best_freep = msu_freep best_damp = msu_damp best_scale = amp_average # best_fit is the RMS misfit between MSU observed # and model amplitudes in log10 space, # an outrageously large value, initially best_fit = 1000000. # an index counter to keep track of the total number of searches/misfits j = 0 # for use with later plotting # prepare array to hold total number of interation results misfits = np.zeros(nscales * ndamps * nfreqs * nloops) # prepare array to store each iteration number misfit_count = np.zeros(nscales * ndamps * nfreqs * nloops) # start of loops for range in search_range: # build a list of "corner frequencies" to test # I think these "corner frequencies" - in ObsPy Speak means 1/free period of seismometer freep_adjust = best_freep * range fp_long = best_freep + freep_adjust fp_short = best_freep - freep_adjust # the case where free period is held constant at the MSU-supplied value if ( nsearch < 3 ): fp_long = best_freep fp_short = best_freep # np.linspace will create an array from low_freq to high_freq over nfreqs of even spacing corners = np.linspace(1./fp_long, 1./fp_short, nfreqs) print ( "\nsearching free periods between %f and %f seconds" % ( fp_long, fp_short ) ) # build a list of damping factors to test damp_adjust = best_damp * range low_damp = best_damp - damp_adjust high_damp = best_damp + damp_adjust if ( high_damp >= 1.0 ): print ( "\nwarning - damping factor is %f - must be below 1.0 - setting damping to 0.9999" % ( high_damp ) ) high_damp = 0.9999 if ( nsearch < 2 ): low_damp = best_damp high_damp = best_damp damps = np.linspace(low_damp, high_damp, ndamps) print ( "searching damping factors between %f and %f" % ( low_damp, high_damp ) ) # build a list of scale factors to test scale_adjust = best_scale * range low_scale = best_scale - scale_adjust high_scale = best_scale + scale_adjust if ( nsearch < 1 ): low_scale = best_scale high_scale = best_scale nscales = 1 scales = np.linspace(low_scale, high_scale, nscales) print ( "searching scale factors between %f and %f" % ( low_scale, high_scale ) ) # here are the grid search loops, over corners, dampings, and scales for corner in corners: for damp in damps: for scale in scales: # the key obspy function to find inst resp based on "corner frequency" and damping constant # cornFreq2Paz takes an instrument's corner freq and damping factor to produce # an Obspy-style paz file resp = sim.cornFreq2Paz(corner, damp) resp['gain'] = scale amp_predicted = np.zeros_like(freq_msu) for i, freq in enumerate(freq_msu): amp_predicted[i] = sim.paz2AmpValueOfFreqResp(resp, freq) misfit = np.linalg.norm(np.log10(amp_msu) - np.log10(amp_predicted)) misfits[j] = misfit misfit_count[j] = j + 1 if ( misfit < best_fit ): best_fit = misfit best_corner = corner best_damp = damp best_scale = scale best_index = j j = j + 1 # find the best free period, which is 1/best_corner # this happens at the end of a particlar grid search loop best_freep = 1./best_corner # end of all loops # find poles and zeros using best corner freq, damp and scale resp = sim.cornFreq2Paz(best_corner, best_damp) resp['gain'] = best_scale return(resp, best_freep, best_damp, best_scale, amp_average, misfits, misfit_count, best_index)
def correct_response(st, removeResp=False, removePAZ=False, simPAZ=False, pre_filt=None, cornFreq=0.0083): """ Correct the seismometer response. Seismometer response is given in either a dictionary ``removeResp'' or a dictionary ``removePAZ''. ``removeResp has precedence. The dictionaries have the following structure removeResp: dictionary with Response information to be removed has the following keys: respfile: (str) filename of evalresp response file. units: (str) Units to return response in. Can be either DIS, VEL or ACC start_stage: (int) integer stage numbers of start stage (<0 causes default evalresp bahaviour). stop_stage: (int) integer stage numbers of stop stage removePAZ: dictionary with poles and zeros to be removed has the following keys: poles: (list of complex numbers) location of poles zeros: (list of complex numbers) location of zeros gain: (float) gain sensitivity: (float) sensitivity It can easily be retrieved with obspy.arclink.client.Client.getPAZ if ``removeResp'' is given the response of each trace must be present in the respfile. If ``removePAZ'' is used the response is assumed to be the same for all traces in the stream. A filter specified in pre_filt can be applied in to avoid amplification of noise. The instrument to be simulated is either described in the dictionary simPAZ or if simPAZ is False by the corner frequency ``cornFreq''. Response correction is done in place and original data is overwritten. The input stream ``st'' should be demeaned and tapered. :type st: obspy.core.stream.Stream :param st: data stream to be corrected :type removeResp: dict :param removeResp: Response information to be removed :type removePAZ: dict :param removePAZ: Response information to be removed :type simPAZ: dict :param simPAZ: Response information to be simulated :type cornFreq: float :param cornFreq: corner frequency of instrument to be simulated :type pre_filt: list :param pre_filt: 4 corners of the filter """ for tr in st: starttime = tr.stats['starttime'] endtime = tr.stats['endtime'] network = tr.stats['network'] station = tr.stats['station'] channel = tr.stats['channel'] location = tr.stats['location'] length = tr.stats['npts'] sampling_rate = tr.stats['sampling_rate'] np2l = nextpow2(2.*length) if not simPAZ: simPAZ = cornFreq2Paz(cornFreq, damp=0.70716) simresp, freqs = np.conj(pazToFreqResp(simPAZ['poles'], simPAZ['zeros'], scale_fac=simPAZ['gain']*simPAZ['sensitivity'], t_samp=1./sampling_rate, nfft=np2l, freq=True)) #see Doc of pazToFreqResp for reason of conj() if removeResp: freqresp, freqs = evalresp(1./sampling_rate,np2l,removeResp['respfile'], starttime, network=network, station=station, channel=channel, locid=location, start_stage=removeResp['start_stage'], stop_stage=removeResp['stop_stage'], units=removeResp['units'], freq=True) else: freqresp, freqs = np.conj(pazToFreqResp(removePAZ['poles'], removePAZ['zeros'], scale_fac=removePAZ['gain']*removePAZ['sensitivity'], t_samp=1./sampling_rate, nfft=np2l, freq=True)) #see Doc of pazToFreqResp for reason of conj() ftr = np.fft.rfft(tr.data,n=np2l) ftr /= freqresp ftr[0] = 0.j # correct the NaN in the DC component ftr *= simresp if pre_filt: ftr *= c_sac_taper(freqs, flimit=pre_filt) tr.data = np.fft.irfft(ftr) tr.trim(starttime,endtime) return
def correct_response(st, removeResp=False, removePAZ=False, simPAZ=False, pre_filt=None, cornFreq=0.0083): """ Correct the seismometer response. Seismometer response is given in either a dictionary ``removeResp'' or a dictionary ``removePAZ''. ``removeResp has precedence. The dictionaries have the following structure removeResp: dictionary with Response information to be removed has the following keys: respfile: (str) filename of evalresp response file. units: (str) Units to return response in. Can be either DIS, VEL or ACC start_stage: (int) integer stage numbers of start stage (<0 causes default evalresp bahaviour). stop_stage: (int) integer stage numbers of stop stage removePAZ: dictionary with poles and zeros to be removed has the following keys: poles: (list of complex numbers) location of poles zeros: (list of complex numbers) location of zeros gain: (float) gain sensitivity: (float) sensitivity It can easily be retrieved with obspy.arclink.client.Client.getPAZ if ``removeResp'' is given the response of each trace must be present in the respfile. If ``removePAZ'' is used the response is assumed to be the same for all traces in the stream. A filter specified in pre_filt can be applied in to avoid amplification of noise. The instrument to be simulated is either described in the dictionary simPAZ or if simPAZ is False by the corner frequency ``cornFreq''. Response correction is done in place and original data is overwritten. The input stream ``st'' should be demeaned and tapered. :type st: obspy.core.stream.Stream :param st: data stream to be corrected :type removeResp: dict :param removeResp: Response information to be removed :type removePAZ: dict :param removePAZ: Response information to be removed :type simPAZ: dict :param simPAZ: Response information to be simulated :type cornFreq: float :param cornFreq: corner frequency of instrument to be simulated :type pre_filt: list :param pre_filt: 4 corners of the filter """ for tr in st: starttime = tr.stats['starttime'] endtime = tr.stats['endtime'] network = tr.stats['network'] station = tr.stats['station'] channel = tr.stats['channel'] location = tr.stats['location'] length = tr.stats['npts'] sampling_rate = tr.stats['sampling_rate'] np2l = nextpow2(2. * length) if not simPAZ: simPAZ = cornFreq2Paz(cornFreq, damp=0.70716) simresp, freqs = np.conj( pazToFreqResp( simPAZ['poles'], simPAZ['zeros'], scale_fac=simPAZ['gain'] * simPAZ['sensitivity'], t_samp=1. / sampling_rate, nfft=np2l, freq=True)) #see Doc of pazToFreqResp for reason of conj() if removeResp: freqresp, freqs = evalresp(1. / sampling_rate, np2l, removeResp['respfile'], starttime, network=network, station=station, channel=channel, locid=location, start_stage=removeResp['start_stage'], stop_stage=removeResp['stop_stage'], units=removeResp['units'], freq=True) else: freqresp, freqs = np.conj( pazToFreqResp( removePAZ['poles'], removePAZ['zeros'], scale_fac=removePAZ['gain'] * removePAZ['sensitivity'], t_samp=1. / sampling_rate, nfft=np2l, freq=True)) #see Doc of pazToFreqResp for reason of conj() ftr = np.fft.rfft(tr.data, n=np2l) ftr /= freqresp ftr[0] = 0.j # correct the NaN in the DC component ftr *= simresp if pre_filt: ftr *= c_sac_taper(freqs, flimit=pre_filt) tr.data = np.fft.irfft(ftr) tr.trim(starttime, endtime) return