Source code for gmprocess.waveform_processing.spectrum
"""Module for computation of theoretical amplitude spectrum methods."""importnumpyasnpfromscipy.optimizeimportminimizefromobspy.geodetics.baseimportgps2dist_azimuthfromgmprocess.waveform_processing.processing_stepimportprocessing_stepOUTPUT_UNITS=["ACC","VEL","DISP"]M_TO_KM=1.0/1000
[docs]@processing_stepdeffit_spectra(st,event,kappa=0.035,RP=0.55,VHC=0.7071068,FSE=2.0,density=2.8,shear_vel=3.7,R0=1.0,moment_factor=100,min_stress=0.1,max_stress=10000,config=None,):"""Fit spectra varying stress_drop and moment. Args: st (gmprocess.core.stationstream.StationStream): Stream of data. event (gmprocess.utils.scalar_event.ScalarEvent): ScalarEvent object. kappa (float): Site diminution factor (sec). Typical value for active crustal regions is about 0.03-0.04, and stable continental regions is about 0.006. RP (float): Partition of shear-wave energy into horizontal components. VHC (float): Partition of shear-wave energy into horizontal components 1 / np.sqrt(2.0). FSE (float): Free surface effect. density (float): Density at source (gm/cc). shear_vel (float): Shear-wave velocity at source (km/s). R0 (float): Reference distance (km). moment_factor (float): Multiplicative factor for setting bounds on moment, where the moment (from the catalog moment magnitude) is multiplied and divided by `moment_factor` to set the bounds for the spectral optimization. min_stress (float): Min stress for fit search (bars). max_stress (float): Max stress for fit search (bars). config (dict): Configuration dictionary (or None). See get_config(). Returns: StationStream with fitted spectra parameters. """fortrinst:iftr.stats.standard.units_type!="acc":tr.fail("Unit type must be acc to fit spectra.")continue# Only do this for horizontal channels for which the smoothed spectra# has been computed.iftr.has_cached("smooth_signal_spectrum")andtr.has_parameter("corner_frequencies"):event_mag=event.magnitudeevent_lon=event.longitudeevent_lat=event.latitudedist=(gps2dist_azimuth(lat1=event_lat,lon1=event_lon,lat2=tr.stats["coordinates"]["latitude"],lon2=tr.stats["coordinates"]["longitude"],)[0]*M_TO_KM)# Use the smoothed spectra for fittingsmooth_signal_dict=tr.get_cached("smooth_signal_spectrum")freq=np.array(smooth_signal_dict["freq"])obs_spec=np.array(smooth_signal_dict["spec"])# -----------------------------------------------------------------# INITIAL VALUES# Need an approximate stress drop as initial guessstress_0=np.sqrt(min_stress*max_stress)moment_0=moment_from_magnitude(event_mag)# Array of initial valuesx0=(np.log(moment_0),np.log(stress_0))# Boundsstress_bounds=(np.log(min_stress),np.log(max_stress))# multiplicative factor for moment boundsmoment_bounds=(x0[0]-np.log(moment_factor),x0[0]+np.log(moment_factor),)bounds=(moment_bounds,stress_bounds)# Frequency limits for cost functionfreq_dict=tr.get_parameter("corner_frequencies")fmin=freq_dict["highpass"]fmax=freq_dict["lowpass"]# -----------------------------------------------------------------# CONSTANT ARGUMENTScargs=(freq,obs_spec,fmin,fmax,dist,kappa,RP,VHC,FSE,shear_vel,density,R0,)result=minimize(spectrum_cost,x0,args=cargs,method="L-BFGS-B",jac=False,bounds=bounds,tol=1e-4,options={"disp":False},)moment_fit=np.exp(result.x[0])magnitude_fit=magnitude_from_moment(moment_fit)stress_drop_fit=np.exp(result.x[1])f0_fit=brune_f0(moment_fit,stress_drop_fit)# Hessian (H) is in terms of normalized moment and stress drop# Covariance matrix is sigma^2 * H^-1.inv_hess=result.hess_inv.todense()# Estimate of sigma^2 is sum of squared residuals / (n - p)# NOTE: we are NOT accounting for the correlation across# frequencies and so we are underestimating the variance.SSR=result.funsigma2=SSR/(len(freq)-len(result.x))COV=sigma2*inv_hesssd=np.sqrt(np.diagonal(COV))# mag_lower = magnitude_from_moment(np.exp(result.x[0]-sd[0]))# mag_upper = magnitude_from_moment(np.exp(result.x[0]+sd[0]))# stress_drop_lower = np.exp(result.x[1]-sd[1])# stress_drop_upper = np.exp(result.x[1]+sd[1])# Get the fitted spectrum and then calculate the goodness-of-fit# metricsfit_spec=model((moment_fit,stress_drop_fit),freq,dist,kappa)mean_squared_error=np.mean((obs_spec-fit_spec)**2)# R^2 (Coefficient of Determination) is defined as 1 minus the# residual sum of squares (SSR) divided by the total sum of squares# (SST)ssr=np.sum((obs_spec-fit_spec)**2)sst=np.sum((obs_spec-np.mean(obs_spec))**2)r_squared=1-(ssr/sst)fit_spectra_dict={"stress_drop":stress_drop_fit,"stress_drop_lnsd":sd[1],"epi_dist":dist,"kappa":kappa,"moment":moment_fit,"moment_lnsd":sd[0],"magnitude":magnitude_fit,"f0":f0_fit,"minimize_message":result.message,"minimize_success":result.success,"mean_squared_error":mean_squared_error,"R2":r_squared,}tr.set_parameter("fit_spectra",fit_spectra_dict)returnst
defspectrum_cost(x,freq,obs_spec,fmin,fmax,dist,kappa,RP,VHC=0.7071068,FSE=2.0,shear_vel=3.7,density=2.8,R0=1.0,gs_mod="REA99",q_mod="REA99",crust_mod="BT15",):""" Function to compute RMS log residuals for optimization. Args: x (tuple): Tuple of the moment (dyne-cm) and the stress drop (bars). freq (array): Numpy array of frequencies (Hz). obs_spec (array): Numpy array of observed Fourier spectral amplitudes. fmin (float): Minimum frequency to use in computing residuals. fmax (float): Maximum frequency to use in computing residuals. dist (float): Distance (km). kappa (float): Site diminution factor (sec). Typical value for active crustal regions is about 0.03-0.04, and stable continental regions is about 0.006. RP (float): Partition of shear-wave energy into horizontal components. VHC (float): Partition of shear-wave energy into horizontal components 1 / np.sqrt(2.0). FSE (float): Free surface effect. shear_vel (float): Shear-wave velocity at source (km/s). density (float): Density at source (gm/cc). R0 (float): Reference distance (km). gs_model (str): Name of model for geometric attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) q_model (str): Name of model for anelastic attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) - 'none' for no anelastic attenuation crust_mod (str): Name of model for crustal amplification. Currently only supported value: - 'BT15' for Boore and Thompson (2015) - 'none' for no crustal amplification model. Returns: float: Sum of squared logarithmic residuals. """# Exponentiate the paramtersxexp=[]forxxinx:xexp.append(np.exp(xx))mod_spec=model(xexp,freq,dist,kappa,RP,VHC,FSE,shear_vel,density,R0,gs_mod,q_mod,crust_mod,)# Remove non-positive values of obs_spec and apply corner frequency# constraintskeep=(obs_spec>0)&(freq>=fmin)&(freq<=fmax)log_residuals=np.log(obs_spec[keep])-np.log(mod_spec[keep])returnnp.sum(log_residuals**2)defmodel(x,freq,dist,kappa,RP=0.55,VHC=0.7071068,FSE=2.0,shear_vel=3.7,density=2.8,R0=1.0,gs_mod="REA99",q_mod="REA99",crust_mod="BT15",):""" Piece together a model of the ground motion spectrum. Args: x (tuple): Tuple of the natural log of moment (dyne-cm) and the natural log of stress drop (bars). freq (array): Numpy array of frequencies for computing spectra (Hz). dist (float): Distance (km). kappa (float): Site diminution factor (sec). Typical value for active crustal regions is about 0.03-0.04, and stable continental regions is about 0.006. RP (float): Partition of shear-wave energy into horizontal components. VHC (float): Partition of shear-wave energy into horizontal components 1 / np.sqrt(2.0). FSE (float): Free surface effect. shear_vel (float): Shear-wave velocity at source (km/s). density (float): Density at source (gm/cc). R0 (float): Reference distance (km). gs_model (str): Name of model for geometric attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) q_model (str): Name of model for anelastic attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) - 'none' for no anelastic attenuation crust_mod (str): Name of model for crustal amplification. Currently only supported value: - 'BT15' for Boore and Thompson (2015) - 'none' for no crustal amplification model. Returns: Array of spectra model. """source_mod=brune(freq,x[0],x[1],RP=RP,VHC=VHC,FSE=FSE,shear_vel=shear_vel,density=density,R0=R0,)path_mod=path(freq,dist,gs_mod,q_mod)site_mod=site(freq,kappa,crust_mod)returnsource_mod*path_mod*site_moddefbrune(freq,moment,stress_drop=150,RP=0.55,VHC=0.7071068,FSE=2.0,shear_vel=3.7,density=2.8,R0=1.0,output_units="ACC",):""" Compute Brune (1970, 1971) earthquake source spectrum. Args: freq (array): Numpy array of frequencies for computing spectra (Hz). moment (float): Earthquake moment (dyne-cm). stress_drop (float): Earthquake stress drop (bars). RP (float): Partition of shear-wave energy into horizontal components. VHC (float): Partition of shear-wave energy into horizontal components 1 / np.sqrt(2.0). FSE (float): Free surface effect. shear_vel (float): Shear-wave velocity at source (km/s). density (float): Density at source (gm/cc). R0 (float): Reference distance (km). output_units (str): Time domain equivalent units for the output spectrum. One of: - "ACC" for acceleration, giving Fourier spectra units of cm/s^2. - "VEL" for velocity, giving Fourier spectra units of cm/s. - "DISP" for displacement, giving Fourier spectra units of cm. Returns: Array of source spectra. """ifoutput_unitsnotinOUTPUT_UNITS:raiseValueError("Unsupported value for output_units.")f0=brune_f0(moment,stress_drop,shear_vel)S=1/(1+(freq/f0)**2)C=RP*VHC*FSE/(4*np.pi*density*shear_vel**3*R0)*1e-20ifoutput_units=="ACC":fpow=2.0elifoutput_units=="VEL":fpow=1.0elifoutput_units=="DISP":fpow=0.0displacement=C*moment*Sreturn(2*np.pi*freq)**fpow*displacementdefbrune_f0(moment,stress_drop,shear_vel=3.7):""" Compute Brune's corner frequency. Args: moment (float): Earthquake moment (dyne-cm). stress_drop (float): Earthquake stress drop (bars). shear_vel (float): Shear-wave velocity at source (km/s). Returns: float: Brune corner frequency (Hz). """f0=4.906e6*shear_vel*(stress_drop/moment)**(1.0/3.0)returnf0defbrune_stress(moment,f0,shear_vel=3.7):""" Compute Brune's stress drop. Args: moment (float): Earthquake moment (dyne-cm). f0 (float): Brune corner frequency (Hz). shear_vel (float): Shear-wave velocity at source (km/s). Returns: float: Brune stress drop (bars). """stress_drop=((f0/(4.906e6*shear_vel))**3)*momentreturnstress_dropdefmoment_from_magnitude(magnitude):""" Compute moment from moment magnitude. Args: magnitude (float): Moment magnitude. Returns: float: Seismic moment (dyne-cm). """return10**(1.5*magnitude+16.05)defmagnitude_from_moment(moment):""" Compute moment from moment magnitude. Args: moment (float): Seismic moment (dyne-cm). Returns: float: Moment magnitude. """return2.0/3.0*(np.log10(moment)-16.05)defpath(freq,dist,gs_mod="REA99",q_mod="REA99"):""" Path term, including geometric and anelastic attenuation. Args: freq (array): Numpy array of frequencies for computing spectra (Hz). dist (float): Distance (km). gs_model (str): Name of model for geometric attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) q_model (str): Name of model for anelastic attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) - 'none' for no anelastic attenuation Returns: Array of path effects. """geom_spread=geometrical_spreading(freq,dist,model=gs_mod)ae_att=anelastic_attenuation(freq,dist,model=q_mod)returngeom_spread*ae_attdefsite(freq,kappa,crust_mod="BT15"):""" Site term, including crustal amplification and kappa. Args: freq (array): Numpy array of frequencies for computing spectra (Hz). kappa (float): Site diminution factor (sec). Typical value for active crustal regions is about 0.03-0.04, and stable continental regions is about 0.006. crust_mod (str): Name of model for crustal amplification. Currently only supported value: - 'BT15' for Boore and Thompson (2015) - 'none' for no crustal amplification model. """crust_amp=crustal_amplification(freq,model=crust_mod)dim=np.exp(-np.pi*kappa*freq)returncrust_amp*dimdefcrustal_amplification(freq,model="BT15"):""" Crustal amplification model. Args: freq (array): Numpy array of frequencies for computing spectra (Hz). model (str): Name of model for crustal amplification. Currently only supported value: - 'BT15' for Boore and Thompson (2015) - 'none' for no crustal amplification model. """ifmodel=="BT15":freq_tab=np.array([0.001,0.009,0.025,0.049,0.081,0.15,0.37,0.68,1.11,2.36,5.25,60.3,])amplification_tab=np.array([1.00,1.01,1.03,1.06,1.10,1.19,1.39,1.58,1.77,2.24,2.75,4.49])# Interpolation should be linear freq, log amplificationlog_amp_tab=np.log(amplification_tab)log_amp_interp=np.interp(freq,freq_tab,log_amp_tab)crustal_amps=np.exp(log_amp_interp)elifmodel=="none":crustal_amps=np.ones_like(freq)else:raiseValueError("Unsupported crustal amplification model.")returncrustal_ampsdefgeometrical_spreading(freq,dist,model="REA99"):""" Effect of geometrical spreading. Args: freq (array): Numpy array of frequencies for computing spectra (Hz). dist (float): Distance (km). model (str): Name of model for geometric attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) Returns: geom (float): anelastic attenuation factor. """ifmodel=="REA99":dist_cross=40.0ifdist<=dist_cross:geom=dist**(-1.0)else:geom=(dist*dist_cross)**(-0.5)else:raiseValueError("Unsupported anelastic attenuation model.")returngeomdefanelastic_attenuation(freq,dist,model="REA99"):""" Effect of anelastic attenuation. Args: freq (array): Numpy array of frequencies for computing spectra (Hz). dist (float): Distance (km). model (str): Name of model for anelastic attenuation. Currently only supported value: - 'REA99' for Raoof et al. (1999) - 'none' for no anelastic attenuation Returns: Array of aneastic attenuation factor. """ifmodel=="REA99":# Frequency dependent quality factorquality_factor=180*freq**0.45cq=3.5anelastic=np.exp(-np.pi*freq*dist/quality_factor/cq)elifmodel=="none":anelastic=np.ones_like(freq)else:raiseValueError("Unsupported anelastic attenuation model.")returnanelasticdeffinite_fault_factor(magnitude,model="BT15"):""" Finite fault factor for converting Rrup to an equivalent point source distance. Args: magnitude (float): Earthquake moment magnitude. model (str): Which model to use; currently only suppport "BT15". Returns: float: Adjusted distance. """ifmodel=="BT15":Mt1=5.744Mt2=7.744ifmagnitude<Mt1:c0=0.7497c1=0.4300c2=0.0Mt=Mt1elifmagnitude<Mt2:c0=0.7497c1=0.4300c2=-0.04875Mt=Mt1else:c0=1.4147c1=0.2350c2=0Mt=Mt2logH=c0+c1*(magnitude-Mt)+c2*(magnitude-Mt)**2h=10**(logH)else:raiseValueError("Unsupported finite fault adjustment model.")returnh