Source code for openquake.hazardlib.gsim.nz22.stafford_2022
# -*- coding: utf-8 -*-# vim: tabstop=4 shiftwidth=4 softtabstop=4## Copyright (C) 2012-2025 GEM Foundation## OpenQuake is free software: you can redistribute it and/or modify it# under the terms of the GNU Affero General Public License as published# by the Free Software Foundation, either version 3 of the License, or# (at your option) any later version.## OpenQuake is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU Affero General Public License for more details.## You should have received a copy of the GNU Affero General Public License# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>."""Module exports :class:`Stafford2022`"""importnumpyasnpfromscipyimportstatsfromopenquake.hazardlib.gsim.baseimportGMPE,CoeffsTablefromopenquake.hazardlibimportconstfromopenquake.hazardlib.imtimportPGA,SAfromopenquake.hazardlib.gsim.chiou_youngs_2014import(_get_centered_ztor,get_hanging_wall_term,get_geometric_spreading,get_magnitude_scaling,get_directivity,_get_basin_term,get_linear_site_term,get_nonlinear_site_term,)CONSTANTS={"c2":1.06,"c4":-2.1,"c4a":-0.5,"crb":50.0,"c8a":0.2695,"c11":0.0,"phi6":300.0,"phi6jp":800.0,}def_sigmoid1d(x,start,finish,centre,slope):""" Returns 1D sigmoid function allowing a smooth transition from `start` to `finish`. The transition is centred at `centre` and changes at a rate linked to `slope` """amplitude=finish-starty=start+amplitude/(1.0+np.exp(-(x-centre)/slope))returnydef_empirical_sigma(T,r,m,start=0.4,finish=0.15):""" Epistemic standard deviation associated with empirical constraint. Takes period `T`, rupture distance `r` and magnitude `m`. """centre_r=_sigmoid1d(T,3.75,5.75,3.5,0.5)slope_r=_sigmoid1d(T,0.225,1.3,3.5,0.5)centre_m=_sigmoid1d(T,5.82,6.4,3.5,0.5)slope_m=_sigmoid1d(T,0.42,0.38,2.5,0.2)amplitude=finish-starts_lnr=_sigmoid1d(np.log(np.clip(r,10**-10,None)),0.0,1.0,centre_r,slope_r)s_m=_sigmoid1d(m,1.0,0.0,centre_m,slope_m)y=start+amplitude*s_lnr*s_mreturnydef_saturation_sigma(m,r):""" Standard deviation of the near-source saturation model. Takes a magnitude `m` and rupture distance `r` """return_sigmoid1d(np.log(np.clip(r,10**-10,None)),0.6638-0.2570*(np.clip(m,-np.inf,7.0)-6.0),0.0,0.7990,0.9835,)def_anelastic_correction(T):""" Mean anelastic attenuation correction -- a function of period `T` """returnnp.clip(0.0024*np.tanh(0.8*np.log(T/1.8)),-np.inf,0.0)def_anelastic_sigma(T):""" Standard deviation of the anelastic attenuation coefficient -- a function of period `T` """return_sigmoid1d(np.log(T),0.00054,0.0004,np.log(0.9),0.2)def_between_event(T,M):""" Between-event standard deviation, function of period `T` and magnitude `M` """# between eventβ0_0=0.45β0_1=0.35β1=0.4β2=0.0β3=0.2τ0=_sigmoid1d(np.log(T),β0_0,β1,β2,β3)τ1=_sigmoid1d(np.log(T),β0_1,β1,β2,β3)τ=τ0+(τ1-τ0)*(np.clip(M,5.0,6.5)-5.0)/1.5returnτdef_between_station(T):""" Between-station standard deviation, function of period `T` """β0_S2S=0.3171β1_S2S=0.3248β2_S2S=0.7911β3_S2S=0.8970β4_S2S=0.7170ϕS2S=β0_S2S+β1_S2S*(T/β4_S2S)**(β2_S2S)*np.exp(-β3_S2S*(T/β4_S2S))returnϕS2Sdef_neff_model(T):""" neff_model(T) Model for the effective number of observations in NZ data, function of period `T` """β0=190.34β1=221.16β2=-2.4341β3=0.10366β4=0.10299ifT<=0.3:return_sigmoid1d(np.log(T),β0,β1,β2,β3)else:returnβ1/((T/0.3)**β4)
[docs]defget_adjustments(T,ctx,adjust_c1,adjust_chm,adjust_c7,adjust_cg1):ρEhEx=0.4epistemic_scale_factor=0.893# The scale factor of 0.9 is applied based upon the discussion that it# accounts for the reduction in epistemic uncertainty when no perfect# correlation is assumed between rupture scenarios. See the note of Peter# and Brendon on slack.MEAN_ADJUSTMENT_TERMS_IF={"Lower":{"delta_c1":(-1.28155*epistemic_scale_factor*_empirical_sigma(T,ctx.rrup,ctx.mag)ifadjust_c1else0.0),"delta_c1hm":(-1.28155*epistemic_scale_factor*ρEhEx*_saturation_sigma(ctx.mag,ctx.rrup)ifadjust_chmelse0.0),"delta_c7":(-0.02578ifadjust_c7else0.0),"delta_c7b":(_sigmoid1d(np.log(T),-0.05737,0.05733,-1.0324,0.04875)ifadjust_c7else0.0),"delta_cg1":(_anelastic_correction(T)-1.28155*epistemic_scale_factor*_anelastic_sigma(T)ifadjust_cg1else0.0),},"Central":{"delta_c1":0.0,"delta_c1hm":0.0,"delta_c7":0.0,"delta_c7b":(_sigmoid1d(np.log(T),-0.0865,0.0,-1.5364,0.3266)ifadjust_c7else0.0),"delta_cg1":(_anelastic_correction(T)ifadjust_cg1else0.0),},"Upper":{"delta_c1":(1.28155*epistemic_scale_factor*_empirical_sigma(T,ctx.rrup,ctx.mag)ifadjust_c1else0.0),"delta_c1hm":(1.28155*epistemic_scale_factor*ρEhEx*_saturation_sigma(ctx.mag,ctx.rrup)ifadjust_chmelse0.0),"delta_c7":0.0,"delta_c7b":0.0,"delta_cg1":(_anelastic_correction(T)+1.28155*epistemic_scale_factor*_anelastic_sigma(T)ifadjust_cg1else0.0),},}returnMEAN_ADJUSTMENT_TERMS_IF
# Modified CY14 fucntions:
[docs]defget_stress_scaling(T,C,ctx,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1):"""This term includes adjustments related to stress scaling."""delta_c1=get_adjustments(T,ctx,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)[mu_branch]["delta_c1"]delta_c1hm=get_adjustments(T,ctx,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)[mu_branch]["delta_c1hm"]returnC["c1"]+delta_c1+delta_c1hm
[docs]defget_far_field_distance_scaling(T,C,ctx,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1):""" Returns the far-field distance scaling term - both magnitude and distance. It includes adjustments made for NZ through delta_cg1. """delta_cg1=get_adjustments(T,ctx,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)[mu_branch]["delta_cg1"]# Get the attenuation distance scalingf_r=(CONSTANTS["c4a"]-CONSTANTS["c4"])*np.log(np.sqrt(ctx.rrup**2.0+CONSTANTS["crb"]**2.0))# Get the magnitude dependent termf_rm=(C["cg1"]+delta_cg1+C["cg2"]/np.cosh(np.clip(ctx.mag-C["cg3"],0.0,None)))returnf_r+f_rm*ctx.rrup
[docs]defget_source_scaling_terms(T,C,ctx,delta_ztor,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1,):""" Returns additional source scaling parameters related to style of faulting, dip and top of rupture depth. It includes adjustments for NZ backbone model through delta_c7 and delta_c7b. """delta_c7=get_adjustments(T,ctx,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)[mu_branch]["delta_c7"]delta_c7b=get_adjustments(T,ctx,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)[mu_branch]["delta_c7b"]f_src=np.zeros_like(ctx.mag)coshm=np.cosh(2.0*np.clip(ctx.mag-4.5,0.0,None))# Style of faulting termpos=(30<=ctx.rake)&(ctx.rake<=150)neg=(-120<=ctx.rake)&(ctx.rake<=-60)# reverse faulting flagf_src[pos]+=C["c1a"]+(C["c1c"]/coshm[pos])# normal faulting flagf_src[neg]+=C["c1b"]+(C["c1d"]/coshm[neg])# Top of rupture termf_src+=((C["c7"]+delta_c7)+((C["c7b"]+delta_c7b)/coshm))*delta_ztor# Dip termf_src+=(CONSTANTS["c11"]+(C["c11b"]/coshm))*np.cos(np.radians(ctx.dip))**2returnf_src
[docs]defget_ln_y_ref(T,C,ctx,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1):""" Returns the ground motion on the reference rock. """delta_ztor=_get_centered_ztor(ctx)return(get_stress_scaling(T,C,ctx,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)+get_magnitude_scaling(C,ctx.mag,delta_cm=0)+get_source_scaling_terms(T,C,ctx,delta_ztor,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1,)+get_hanging_wall_term(C,ctx)+get_geometric_spreading(C,ctx.mag,ctx.rrup)+get_far_field_distance_scaling(T,C,ctx,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)+get_directivity(C,ctx))
[docs]defget_stddevs(T,ln_y_ref,C,ctx,sigma_branch):""" Returns the standard deviation model described in equation 8.16 (Peter Stafford NSHM report) """tau=_between_event(T,ctx.mag)phi_S2S=_between_station(T)phi_SS=0.39NL=_nl_sigma(C,ctx,ln_y_ref)phi=np.sqrt(phi_S2S**2+(1.0+NL)**2*0.736*phi_SS**2+0.264*phi_SS**2)# nominal total standard deviation for the branchsigma=np.sqrt((1.0+NL)**2*tau**2+phi**2)# perform the potential inflation of σ for epistemic uncertaintyneff=_neff_model(T)nfac=(_empirical_sigma(T,ctx.rrup,ctx.mag)/_empirical_sigma(T,80.0,5.0))**2ndof=neff/nfacifsigma_branch=="Lower":sigma=np.sqrt((sigma**2/(ndof-1))*stats.chi2.ppf(0.1,ndof-1))elifsigma_branch=="Central":sigma=np.sqrt((sigma**2/(ndof-1))*stats.chi2.ppf(0.5,ndof-1))elifsigma_branch=="Upper":sigma=np.sqrt((sigma**2/(ndof-1))*stats.chi2.ppf(0.9,ndof-1))else:print("sigma branch not recognised: ""must be one of :Lower, :Central, :Upper")sigma=np.nanreturnsigma,np.abs((1.0+NL)*tau),phi
[docs]defget_mean_stddevs(T,mu_branch,sigma_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1,C,ctx,imt):""" Return mean and standard deviation values. """# Get ground motion on reference rockln_y_ref=get_ln_y_ref(T,C,ctx,mu_branch,adjust_c1,adjust_chm,adjust_c7,adjust_cg1)y_ref=np.exp(ln_y_ref)# Get the site amplification# Get basin depthf_z1pt0=_get_basin_term(C,ctx,"Stafford2022",imt)# Get linear amplification termf_lin=get_linear_site_term("Stafford2022",C,ctx)# Get nonlinear amplification termf_nl,_f_nl_scaling=get_nonlinear_site_term(C,ctx,y_ref)# Add on the site amplificationmean=ln_y_ref+(f_lin+f_nl+f_z1pt0)# Get standard deviationssig,tau,phi=get_stddevs(T,ln_y_ref,C,ctx,sigma_branch)returnmean,sig,tau,phi
[docs]classStafford2022(GMPE):""" Implements Backbone model developed by Peter Stafford for NZ NSHM revision. For more details see Peter Stafford's GNS report. The base model implementation remains the same as for Chiou and Youngs (2014). Chiou, B. S.-J. and Youngs, R. R. (2014), "Updated of the Chiou and Youngs NGA Model for the Average Horizontal Component of Peak Ground Motion and Response Spectra, Earthquake Spectra, 30(3), 1117 - 1153, DOI: 10.1193/072813EQS219M """#: Supported tectonic region type is active shallow crustDEFINED_FOR_TECTONIC_REGION_TYPE=const.TRT.ACTIVE_SHALLOW_CRUST#: Supported intensity measure types are spectral acceleration,#: and peak ground accelerationDEFINED_FOR_INTENSITY_MEASURE_TYPES={PGA,SA}#: Supported intensity measure component is orientation-independent#: measure :attr:`~openquake.hazardlib.const.IMC.RotD50`,DEFINED_FOR_INTENSITY_MEASURE_COMPONENT=const.IMC.RotD50#: Supported standard deviation types are inter-event, intra-event#: and total, see chapter "Variance model".DEFINED_FOR_STANDARD_DEVIATION_TYPES={const.StdDev.TOTAL,const.StdDev.INTER_EVENT,const.StdDev.INTRA_EVENT}#: Required site parameters are Vs30, Vs30 measured flag#: and Z1.0.REQUIRES_SITES_PARAMETERS={"vs30","vs30measured","z1pt0"}#: Required rupture parameters are magnitude, rake,#: dip and ztor.REQUIRES_RUPTURE_PARAMETERS={"dip","rake","mag","ztor"}#: Required distance measures are RRup, Rjb and Rx.REQUIRES_DISTANCES={"rrup","rjb","rx"}#: Reference shear wave velocityDEFINED_FOR_REFERENCE_VELOCITY=1130def__init__(self,mu_branch="Central",sigma_branch="Central",adjust_c1=True,adjust_chm=True,adjust_c7=True,adjust_cg1=True,):""" Additional parameter for epistemic central, lower and upper bounds. """self.mu_branch=mu_branchself.sigma_branch=sigma_branchself.adjust_c1=adjust_c1self.adjust_chm=adjust_chmself.adjust_c7=adjust_c7self.adjust_cg1=adjust_cg1
[docs]defcompute(self,ctx:np.recarray,imts,mean,sig,tau,phi):""" See :meth:`superclass method <.base.GroundShakingIntensityModel.compute>` for spec of input and result values. """form,imtinenumerate(imts):ifrepr(imt)=="PGV":print("Invalid IMT provided. The model does not predict"" ground motions for PGV.")sig[m],tau[m],phi[m]=None,None,Noneelifrepr(imt)=="PGA":pga_mean,pga_sig,pga_tau,pga_phi=get_mean_stddevs(SA(0.01).period,self.mu_branch,self.sigma_branch,self.adjust_c1,self.adjust_chm,self.adjust_c7,self.adjust_cg1,self.COEFFS[SA(0.01)],ctx,imt)# Peter has used T = 0.01 as the period for PGA because the# coefficients (for 0.01s and PGA) are identical.mean[m]=pga_meansig[m],tau[m],phi[m]=pga_sig,pga_tau,pga_phielse:T=imt.periodimt_mean,imt_sig,imt_tau,imt_phi=get_mean_stddevs(T,self.mu_branch,self.sigma_branch,self.adjust_c1,self.adjust_chm,self.adjust_c7,self.adjust_cg1,self.COEFFS[imt],ctx,imt)mean[m]=imt_meansig[m],tau[m],phi[m]=imt_sig,imt_tau,imt_phi