Source code for openquake.hmtk.seismicity.gcmt_catalogue
# -*- coding: utf-8 -*-# vim: tabstop=4 shiftwidth=4 softtabstop=4## LICENSE## Copyright (C) 2010-2023 GEM Foundation, G. Weatherill, M. Pagani,# D. Monelli.## The Hazard Modeller's Toolkit 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.## You should have received a copy of the GNU Affero General Public License# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>## DISCLAIMER## The software Hazard Modeller's Toolkit (openquake.hmtk) provided herein# is released as a prototype implementation on behalf of# scientists and engineers working within the GEM Foundation (Global# Earthquake Model).## It is distributed for the purpose of open collaboration and in the# hope that it will be useful to the scientific, engineering, disaster# risk and software design communities.## The software is NOT distributed as part of GEM’s OpenQuake suite# (https://www.globalquakemodel.org/tools-products) and must be considered as a# separate entity. The software provided herein is designed and implemented# by scientific staff. It is not developed to the design standards, nor# subject to same level of critical review by professional software# developers, as GEM’s OpenQuake software suite.## Feedback and contribution to the software is welcome, and can be# directed to the hazard scientific staff of the GEM Model Facility# (hazard@globalquakemodel.org).## The Hazard Modeller's Toolkit (openquake.hmtk) is therefore distributed# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License# for more details.## The GEM Foundation, and the authors of the software, assume no# liability for use of the software."""Implements sets of classes for mapping components of the focal mechanism"""importcsvimportdatetimeimportnumpyasnpfrommathimportfabs,floor,sqrt,pifromopenquake.hmtk.seismicityimportgcmt_utilsasutilsfromopenquake.hmtk.seismicity.catalogueimportCatalogue
[docs]defcmp(a,b):# Python 3 replacement of Python2 cmpreturn(a>b)-(a<b)
[docs]defcmp_mat(a,b):""" Sorts two matrices returning a positive or zero value """c=0forx,yinzip(a.flat,b.flat):c=cmp(abs(x),abs(y))ifc!=0:returncreturnc
[docs]classGCMTHypocentre(object):""" Simple representation of the hypocentre """def__init__(self):""" """self.source=Noneself.date=Noneself.time=Noneself.longitude=Noneself.latitude=Noneself.depth=Noneself.m_b=Noneself.m_s=Noneself.location=None
[docs]classGCMTCentroid(object):""" Representation of a GCMT centroid """def__init__(self,reference_date,reference_time):""" :param reference_date: Date of hypocentre as instance of :class: datetime.datetime.date :param reference_time: Time of hypocentre as instance of :class: datetime.datetime.time """self.centroid_type=Noneself.source=Noneself.time=reference_timeself.time_error=Noneself.date=reference_dateself.longitude=Noneself.longitude_error=Noneself.latitude=Noneself.latitude_error=Noneself.depth=Noneself.depth_error=Noneself.depth_type=Noneself.centroid_id=Nonedef_get_centroid_time(self,time_diff):""" Calculates the time difference between the date-time classes """source_time=datetime.datetime.combine(self.date,self.time)second_diff=floor(fabs(time_diff))microsecond_diff=int(1000.0*(time_diff-second_diff))iftime_diff<0.0:source_time=source_time-datetime.timedelta(seconds=int(second_diff),microseconds=microsecond_diff)else:source_time=source_time+datetime.timedelta(seconds=int(second_diff),microseconds=microsecond_diff)self.time=source_time.time()self.date=source_time.date()
[docs]classGCMTPrincipalAxes(object):""" Class to represent the eigensystem of the tensor in terms of T-, B- and P- plunge and azimuth """def__init__(self):""" """self.t_axis=Noneself.b_axis=Noneself.p_axis=None
[docs]defget_moment_tensor_from_principal_axes(self):""" Retreives the moment tensor from the prinicpal axes """raiseNotImplementedError("Moment tensor from principal axes not yet ""implemented!")
[docs]defget_azimuthal_projection(self,height=1.0):""" Returns the azimuthal projection of the tensor according to the method of Frohlich (2001) """raiseNotImplementedError("Get azimuthal projection not yet ""implemented!")
[docs]classGCMTMomentTensor(object):""" Class to represent a moment tensor and its associated methods """def__init__(self,reference_frame=None):""" """self.tensor=Noneself.tensor_sigma=Noneself.exponent=Noneself.eigenvalues=Noneself.eigenvectors=Noneifreference_frame:self.ref_frame=reference_frameelse:# Default to USEself.ref_frame="USE"
[docs]defnormalise_tensor(self):""" Normalise the tensor by dividing it by its norm, defined such that np.sqrt(X:X) """self.tensor,tensor_norm=utils.normalise_tensor(self.tensor)returnself.tensor/tensor_norm,tensor_norm
def_to_ned(self):""" Switches the reference frame to NED """ifself.ref_frame=="USE":# Rotatereturnutils.use_to_ned(self.tensor),utils.use_to_ned(self.tensor_sigma)elifself.ref_frame=="NED":# Alreadt NEDreturnself.tensor,self.tensor_sigmaelse:raiseValueError("Reference frame %s not recognised - cannot ""transform to NED!"%self.ref_frame)def_to_use(self):""" Returns a tensor in the USE reference frame """ifself.ref_frame=="NED":# Rotatereturnutils.ned_to_use(self.tensor),utils.ned_to_use(self.tensor_sigma)elifself.ref_frame=="USE":# Already USEreturnself.tensor,self.tensor_sigmaelse:raiseValueError("Reference frame %s not recognised - cannot ""transform to USE!"%self.ref_frame)def_to_6component(self):""" Returns the unique 6-components of the tensor in USE format [Mrr, Mtt, Mpp, Mrt, Mrp, Mtp] """returnutils.tensor_to_6component(self.tensor,self.ref_frame)
[docs]defeigendecompose(self,normalise=False):""" Performs and eigendecomposition of the tensor and orders into descending eigenvalues """self.eigenvalues,self.eigenvectors=utils.eigendecompose(self.tensor,normalise)returnself.eigenvalues,self.eigenvectors
[docs]defget_nodal_planes(self):""" Returns the nodal planes by eigendecomposition of the moment tensor """# Convert reference frame to NEDself.tensor,self.tensor_sigma=self._to_ned()self.ref_frame="NED"# Eigenvalue decomposition# Tensor_,evect=utils.eigendecompose(self.tensor)# Rotation matrix_,rot_vec=utils.eigendecompose(np.array([[0.0,0.0,-1],[0.0,0.0,0.0],[-1.0,0.0,0.0]]))rotation_matrix=(evect@rot_vec.T).Tifnp.linalg.det(rotation_matrix)<0.0:rotation_matrix@=-1.0flip_dc=np.array([[0.0,0.0,-1.0],[0.0,-1.0,0.0],[-1.0,0.0,0.0]])rotation_matrices=sorted([rotation_matrix,flip_dc@rotation_matrix],cmp=cmp_mat)nodal_planes=GCMTNodalPlanes()dip,strike,rake=[(180.0/pi)*angleforangleinutils.matrix_to_euler(rotation_matrices[0])]# 1st Nodal Planenodal_planes.nodal_plane_1={"strike":strike%360,"dip":dip,"rake":-rake,}# 2nd Nodal Planedip,strike,rake=[(180.0/pi)*angleforangleinutils.matrix_to_euler(rotation_matrices[1])]nodal_planes.nodal_plane_2={"strike":strike%360.0,"dip":dip,"rake":-rake,}returnnodal_planes
[docs]defget_principal_axes(self):""" Uses the eigendecomposition to extract the principal axes from the moment tensor - returning an instance of the GCMTPrincipalAxes class """# Perform eigendecomposition - returns in order P, B, T_=self.eigendecompose(normalise=True)principal_axes=GCMTPrincipalAxes()# Eigenvaluesprincipal_axes.p_axis={"eigenvalue":self.eigenvalues[0]}principal_axes.b_axis={"eigenvalue":self.eigenvalues[1]}principal_axes.t_axis={"eigenvalue":self.eigenvalues[2]}# Eigen vectors# 1) P axisazim,plun=utils.get_azimuth_plunge(self.eigenvectors[:,0],True)principal_axes.p_axis["azimuth"]=azimprincipal_axes.p_axis["plunge"]=plun# 2) B axisazim,plun=utils.get_azimuth_plunge(self.eigenvectors[:,1],True)principal_axes.b_axis["azimuth"]=azimprincipal_axes.b_axis["plunge"]=plun# 3) T axisazim,plun=utils.get_azimuth_plunge(self.eigenvectors[:,2],True)principal_axes.t_axis["azimuth"]=azimprincipal_axes.t_axis["plunge"]=plunreturnprincipal_axes
[docs]classGCMTEvent(object):""" Class to represent full GCMT moment tensor in ndk format """def__init__(self):""" """self.identifier=Noneself.hypocentre=Noneself.centroid=Noneself.magnitude=Noneself.moment=Noneself.metadata={}self.moment_tensor=Noneself.nodal_planes=Noneself.principal_axes=Noneself.f_clvd=Noneself.e_rel=None
[docs]defget_f_clvd(self):""" Returns the statistic f_clvd: the signed ratio of the sizes of the intermediate and largest principal moments:: f_clvd = -b_axis_eigenvalue / max(|t_axis_eigenvalue|,|p_axis_eigenvalue|) """ifnotself.principal_axes:# Principal axes not yet defined for moment tensor - raises errorraiseValueError("Principal Axes not defined!")denominator=np.max(np.array([fabs(self.principal_axes.t_axis["eigenvalue"]),fabs(self.principal_axes.p_axis["eigenvalue"]),]))self.f_clvd=-self.principal_axes.b_axis["eigenvalue"]/denominatorreturnself.f_clvd
[docs]defget_relative_error(self):""" Returns the relative error statistic (e_rel), defined by Frohlich & Davis (1999): `e_rel = sqrt((U:U) / (M:M))` where M is the moment tensor, U is the uncertainty tensor and : is the tensor dot product """ifnotself.moment_tensor:raiseValueError("Moment tensor not defined!")numer=np.tensordot(self.moment_tensor.tensor_sigma,self.moment_tensor.tensor_sigma)denom=np.tensordot(self.moment_tensor.tensor,self.moment_tensor.tensor)self.e_rel=sqrt(numer/denom)returnself.e_rel
[docs]classGCMTNodalPlanes(object):""" Class to represent the nodal plane distribution of the tensor Each nodal plane is represented as a dictionary of the form: {'strike':, 'dip':, 'rake':} """def__init__(self):""" """self.nodal_plane_1=Noneself.nodal_plane_2=None
[docs]classGCMTCatalogue(Catalogue):""" Class to hold a catalogue of moment tensors """FLOAT_ATTRIBUTE_LIST=["second","timeError","longitude","latitude","SemiMajor90","SemiMinor90","ErrorStrike","depth","depthError","magnitude","sigmaMagnitude","moment","strike1","rake1","dip1","strike2","rake2","dip2","eigenvalue_b","azimuth_b","plunge_b","eigenvalue_p","azimuth_p","plunge_p","eigenvalue_t","azimuth_t","plunge_t","f_clvd","e_rel",]INT_ATTRIBUTE_LIST=["eventID","year","month","day","hour","minute","flag",]STRING_ATTRIBUTE_LIST=["Agency","magnitudeType","comment","centroidID",]TOTAL_ATTRIBUTE_LIST=list((set(FLOAT_ATTRIBUTE_LIST).union(set(INT_ATTRIBUTE_LIST))).union(set(STRING_ATTRIBUTE_LIST)))def__init__(self,start_year=None,end_year=None):""" Instantiate catalogue class """super(GCMTCatalogue,self).__init__()self.gcmts=[]self.number_gcmts=Noneself.start_year=start_yearself.end_year=end_yearforattributeinself.TOTAL_ATTRIBUTE_LIST:ifattributeinself.FLOAT_ATTRIBUTE_LIST:self.data[attribute]=np.array([],dtype=float)elifattributeinself.INT_ATTRIBUTE_LIST:self.data[attribute]=np.array([],dtype=int)
[docs]defget_number_tensors(self):""" Returns number of CMTs """returnlen(self.gcmts)
[docs]defselect_catalogue_events(self,id0):""" Orders the events in the catalogue according to an indexing vector :param np.ndarray id0: Pointer array indicating the locations of selected events """forkeyinself.data.keys():if(isinstance(self.data[key],np.ndarray)andlen(self.data[key])>0):# Dictionary element is numpy array - use logical indexingself.data[key]=self.data[key][id0]elifisinstance(self.data[key],list)andlen(self.data[key])>0:# Dictionary element is listself.data[key]=[self.data[key][iloc]forilocinid0]else:continueiflen(self.gcmts)>0:self.gcmts=[self.gcmts[iloc]forilocinid0]self.number_gcmts=self.get_number_tensors()
[docs]defserialise_to_hmtk_csv(self,filename,centroid_location=True):""" Serialise the catalogue to a simple csv format, designed for comptibility with the GEM Hazard Modeller's Toolkit """header_list=["eventID","Agency","year","month","day","hour","minute","second","timeError","longitude","latitude","SemiMajor90","SemiMinor90","ErrorStrike","depth","depthError","magnitude","sigmaMagnitude",]withopen(filename,"wt",newline="")asfid:writer=csv.DictWriter(fid,fieldnames=header_list)headers=dict((header,header)forheaderinheader_list)writer.writerow(headers)print("Writing to simple csv format ...")foriloc,tensorinenumerate(self.gcmts):# Generic Datacmt_dict={"eventID":iloc+100000,"Agency":"GCMT","SemiMajor90":None,"SemiMinor90":None,"ErrorStrike":None,"magnitude":tensor.magnitude,"sigmaMagnitude":None,"depth":None,"depthError":None,}ifcentroid_location:# Time and location come from centroidcmt_dict["year"]=tensor.centroid.date.yearcmt_dict["month"]=tensor.centroid.date.monthcmt_dict["day"]=tensor.centroid.date.daycmt_dict["hour"]=tensor.centroid.time.hourcmt_dict["minute"]=tensor.centroid.time.minutecmt_dict["second"]=np.round(float(tensor.centroid.time.second)+float(tensor.centroid.time.microsecond)/1.0e6,2,)cmt_dict["timeError"]=tensor.centroid.time_errorcmt_dict["longitude"]=tensor.centroid.longitudecmt_dict["latitude"]=tensor.centroid.latitudecmt_dict["depth"]=tensor.centroid.depthcmt_dict["depthError"]=tensor.centroid.depth_errorelse:# Time and location come from hypocentrecmt_dict["year"]=tensor.hypocentre.date.yearcmt_dict["month"]=tensor.hypocentre.date.monthcmt_dict["day"]=tensor.hypocentre.date.daycmt_dict["hour"]=tensor.hypocentre.time.hourcmt_dict["minute"]=tensor.hypocentre.time.minutecmt_dict["second"]=np.round(float(tensor.hypocentre.time.second)+float(tensor.hypocentre.time.microsecond)/1000000.0,2,)cmt_dict["timeError"]=Nonecmt_dict["longitude"]=tensor.hypocentre.longitudecmt_dict["latitude"]=tensor.hypocentre.latitudecmt_dict["depth"]=tensor.hypocentre.depthcmt_dict["depthError"]=Nonewriter.writerow(cmt_dict)print("done!")
[docs]defgcmt_to_simple_array(self,centroid_location=True):""" Converts the GCMT catalogue to a simple array of [ID, year, month, day, hour, minute, second, long., lat., depth, Mw, strike1, dip1, rake1, strike2, dip2, rake2, b-plunge, b-azimuth, b-eigenvalue, p-plunge, p-azimuth, p-eigenvalue, t-plunge, t-azimuth, t-eigenvalue, moment, f_clvd, erel] """catalogue=np.zeros([self.get_number_tensors(),29],dtype=float)foriloc,tensorinenumerate(self.gcmts):catalogue[iloc,0]=ilocifcentroid_location:catalogue[iloc,1]=float(tensor.centroid.date.year)catalogue[iloc,2]=float(tensor.centroid.date.month)catalogue[iloc,3]=float(tensor.centroid.date.day)catalogue[iloc,4]=float(tensor.centroid.time.hour)catalogue[iloc,5]=float(tensor.centroid.time.minute)catalogue[iloc,6]=np.round(float(tensor.centroid.time.second)+float(tensor.centroid.time.microsecond)/1000000.0,2,)catalogue[iloc,7]=tensor.centroid.longitudecatalogue[iloc,8]=tensor.centroid.latitudecatalogue[iloc,9]=tensor.centroid.depthelse:catalogue[iloc,1]=float(tensor.hypocentre.date.year)catalogue[iloc,2]=float(tensor.hypocentre.date.month)catalogue[iloc,3]=float(tensor.hypocentre.date.day)catalogue[iloc,4]=float(tensor.hypocentre.time.hour)catalogue[iloc,5]=float(tensor.hypocentre.time.minute)catalogue[iloc,6]=np.round(float(tensor.centroid.time.second)+float(tensor.centroid.time.microsecond)/1000000.0,2,)catalogue[iloc,7]=tensor.hypocentre.longitudecatalogue[iloc,8]=tensor.hypocentre.latitudecatalogue[iloc,9]=tensor.hypocentre.depthcatalogue[iloc,10]=tensor.magnitudecatalogue[iloc,11]=tensor.momentcatalogue[iloc,12]=tensor.f_clvdcatalogue[iloc,13]=tensor.e_rel# Nodal planescatalogue[iloc,14]=tensor.nodal_planes.nodal_plane_1["strike"]catalogue[iloc,15]=tensor.nodal_planes.nodal_plane_1["dip"]catalogue[iloc,16]=tensor.nodal_planes.nodal_plane_1["rake"]catalogue[iloc,17]=tensor.nodal_planes.nodal_plane_2["strike"]catalogue[iloc,18]=tensor.nodal_planes.nodal_plane_2["dip"]catalogue[iloc,19]=tensor.nodal_planes.nodal_plane_2["rake"]# Principal axescatalogue[iloc,20]=tensor.principal_axes.b_axis["eigenvalue"]catalogue[iloc,21]=tensor.principal_axes.b_axis["azimuth"]catalogue[iloc,22]=tensor.principal_axes.b_axis["plunge"]catalogue[iloc,23]=tensor.principal_axes.p_axis["eigenvalue"]catalogue[iloc,24]=tensor.principal_axes.p_axis["azimuth"]catalogue[iloc,25]=tensor.principal_axes.p_axis["plunge"]catalogue[iloc,26]=tensor.principal_axes.t_axis["eigenvalue"]catalogue[iloc,27]=tensor.principal_axes.t_axis["azimuth"]catalogue[iloc,28]=tensor.principal_axes.t_axis["plunge"]returncatalogue