# -*- coding: utf-8 -*-# -------------------------------------------------------------------# Filename: beachball.py# Purpose: Draws a beach ball diagram of an earthquake focal mechanism.# Author: Robert Barsch# Email: barsch@geophysik.uni-muenchen.de## Copyright (C) 2008-2012 Robert Barsch# ---------------------------------------------------------------------"""Draws a beachball diagram of an earthquake focal mechanismMost source code provided here are adopted from1. MatLab script `bb.m`_ written by Andy Michael and Oliver Boyd.2. ps_meca program from the `Generic Mapping Tools (GMT)`_.:copyright: The ObsPy Development Team (devs@obspy.org):license: GNU General Public License (GPL) (http://www.gnu.org/licenses/gpl.txt).. _`Generic Mapping Tools (GMT)`: http://gmt.soest.hawaii.edu.. _`bb.m`: http://www.ceri.memphis.edu/people/olboyd/Software/Software.html"""importioimportmatplotlib.pyplotaspltfrommatplotlibimportpatches,collections,transforms,pathasmplpathimportnumpyasnpfromopenquake.baselib.generalimportParamD2R=np.pi/180R2D=180/np.piEPSILON=0.00001
[docs]defBeach(fm,linewidth=2,facecolor="b",bgcolor="w",edgecolor="k",alpha=1.0,xy=(0,0),width=200,size=100,nofill=False,zorder=100,axes=None):""" Return a beach ball as a collection which can be connected to an current matplotlib axes instance (ax.add_collection). S1, D1, and R1, the strike, dip and rake of one of the focal planes, can be vectors of multiple focal mechanisms. :param fm: Focal mechanism that is either number of mechanisms (NM) by 3 (strike, dip, and rake) or NM x 6 (M11, M22, M33, M12, M13, M23 - the six independent components of the moment tensor, where the coordinate system is 1,2,3 = Up,South,East which equals r,theta,phi). The strike is of the first plane, clockwise relative to north. The dip is of the first plane, defined clockwise and perpendicular to strike, relative to horizontal such that 0 is horizontal and 90 is vertical. The rake is of the first focal plane solution. 90 moves the hanging wall up-dip (thrust), 0 moves it in the strike direction (left-lateral), -90 moves it down-dip (normal), and 180 moves it opposite to strike (right-lateral). :param facecolor: Color to use for quadrants of tension; can be a string, e.g. ``'r'``, ``'b'`` or three component color vector, [R G B]. Defaults to ``'b'`` (blue). :param bgcolor: The background color. Defaults to ``'w'`` (white). :param edgecolor: Color of the edges. Defaults to ``'k'`` (black). :param alpha: The alpha level of the beach ball. Defaults to ``1.0`` (opaque). :param xy: Origin position of the beach ball as tuple. Defaults to ``(0, 0)``. :type width: int or tuple :param width: Symbol size of beach ball, or tuple for elliptically shaped patches. Defaults to size ``200``. :param size: Controls the number of interpolation points for the curves. Minimum is automatically set to ``100``. :param nofill: Do not fill the beach ball, but only plot the planes. :param zorder: Set zorder. Artists with lower zorder values are drawn first. :type axes: :class:`matplotlib.axes.Axes` :param axes: Used to make beach balls circular on non-scaled axes. Also maintains the aspect ratio when resizing the figure. Will not add the returned collection to the axes instance. """# check if one or two widths are specified (Circle or Ellipse)try:assertlen(width)==2exceptTypeError:width=(width,width)mt=Nonenp1=Noneifisinstance(fm,MomentTensor):mt=fmnp1=MT2Plane(mt)elifisinstance(fm,NodalPlane):np1=fmeliflen(fm)==6:mt=MomentTensor(fm[0],fm[1],fm[2],fm[3],fm[4],fm[5],0)np1=MT2Plane(mt)eliflen(fm)==3:np1=NodalPlane(fm[0],fm[1],fm[2])else:raiseTypeError("Wrong input value for 'fm'.")# Only at least size 100, i.e. 100 points in the matrix are allowedifsize<100:size=100# Return as collectionifmt:(T,N,P)=MT2Axes(mt)ifnp.fabs(N.val)<EPSILONandnp.fabs(T.val+P.val)<EPSILON:colors,p=plotDC(np1,size,xy=xy,width=width)else:colors,p=plotMT(T,N,P,size,plot_zerotrace=True,xy=xy,width=width)else:colors,p=plotDC(np1,size=size,xy=xy,width=width)ifnofill:# XXX: not tested with plotMTcol=collections.PatchCollection([p[1]],match_original=False)col.set_facecolor("none")else:col=collections.PatchCollection(p,match_original=False)# Replace color dummies 'b' and 'w' by face and bgcolorfc=[facecolorifc=="b"elsebgcolorforcincolors]col.set_facecolors(fc)# Use the given axes to maintain the aspect ratio of beachballs on figure# resize.ifaxesisnotNone:# This is what holds the aspect ratio (but breaks the positioning)col.set_transform(transforms.IdentityTransform())# Next is a dirty hack to fix the positioning:# 1. Need to bring the all patches to the origin (0, 0).forpincol._paths:p.vertices-=xy# 2. Then use the offset property of the collection to position the# patchescol.set_offsets(xy)col._transOffset=axes.transDatacol.set_edgecolor(edgecolor)col.set_alpha(alpha)col.set_linewidth(linewidth)col.set_zorder(zorder)returncol
[docs]defBeachball(fm,linewidth=2,facecolor="b",bgcolor="w",edgecolor="k",alpha=1.0,xy=(0,0),width=200,size=100,nofill=False,zorder=100,outfile=None,format=None,fig=None):""" Draws a beach ball diagram of an earthquake focal mechanism. S1, D1, and R1, the strike, dip and rake of one of the focal planes, can be vectors of multiple focal mechanisms. :param fm: Focal mechanism that is either number of mechanisms (NM) by 3 (strike, dip, and rake) or NM x 6 (M11, M22, M33, M12, M13, M23 - the six independent components of the moment tensor, where the coordinate system is 1,2,3 = Up,South,East which equals r,theta,phi). The strike is of the first plane, clockwise relative to north. The dip is of the first plane, defined clockwise and perpendicular to strike, relative to horizontal such that 0 is horizontal and 90 is vertical. The rake is of the first focal plane solution. 90 moves the hanging wall up-dip (thrust), 0 moves it in the strike direction (left-lateral), -90 moves it down-dip (normal), and 180 moves it opposite to strike (right-lateral). :param facecolor: Color to use for quadrants of tension; can be a string, e.g. ``'r'``, ``'b'`` or three component color vector, [R G B]. Defaults to ``'b'`` (blue). :param bgcolor: The background color. Defaults to ``'w'`` (white). :param edgecolor: Color of the edges. Defaults to ``'k'`` (black). :param alpha: The alpha level of the beach ball. Defaults to ``1.0`` (opaque). :param xy: Origin position of the beach ball as tuple. Defaults to ``(0, 0)``. :type width: int :param width: Symbol size of beach ball. Defaults to ``200``. :param size: Controls the number of interpolation points for the curves. Minimum is automatically set to ``100``. :param nofill: Do not fill the beach ball, but only plot the planes. :param zorder: Set zorder. Artists with lower zorder values are drawn first. :param outfile: Output file string. Also used to automatically determine the output format. Supported file formats depend on your matplotlib backend. Most backends support png, pdf, ps, eps and svg. Defaults to ``None``. :param format: Format of the graph picture. If no format is given the outfile parameter will be used to try to automatically determine the output format. If no format is found it defaults to png output. If no outfile is specified but a format is, than a binary imagestring will be returned. Defaults to ``None``. :param fig: Give an existing figure instance to plot into. New Figure if set to ``None``. """plot_width=width*0.95# plot the figureifnotfig:fig=plt.figure(figsize=(3,3),dpi=100)fig.subplots_adjust(left=0,bottom=0,right=1,top=1)fig.set_figheight(width//100)fig.set_figwidth(width//100)ax=fig.add_subplot(111,aspect="equal")# hide axes + ticksax.axison=False# plot the collectioncollection=Beach(fm,linewidth=linewidth,facecolor=facecolor,edgecolor=edgecolor,bgcolor=bgcolor,alpha=alpha,nofill=nofill,xy=xy,width=plot_width,size=size,zorder=zorder,)ax.add_collection(collection)ax.autoscale_view(tight=False,scalex=True,scaley=True)# exportifoutfile:ifformat:fig.savefig(outfile,dpi=100,transparent=True,format=format)else:fig.savefig(outfile,dpi=100,transparent=True)elifformatandnotoutfile:imgdata=io.BytesIO()fig.savefig(imgdata,format=format,dpi=100,transparent=True)imgdata.seek(0)returnimgdata.read()else:plt.show()returnfig
[docs]defplotMT(T,N,P,size=200,plot_zerotrace=True,x0=0,y0=0,xy=(0,0),width=200):""" Uses a principal axis T, N and P to draw a beach ball plot. :param ax: axis object of a matplotlib figure :param T: :class:`~PrincipalAxis` :param N: :class:`~PrincipalAxis` :param P: :class:`~PrincipalAxis` Adapted from ps_tensor / utilmeca.c / `Generic Mapping Tools (GMT)`_. .. _`Generic Mapping Tools (GMT)`: http://gmt.soest.hawaii.edu """# check if one or two widths are specified (Circle or Ellipse)try:assertlen(width)==2exceptTypeError:width=(width,width)collect=[]colors=[]p=Param(# 28 parameters!azi=np.zeros((3,2)),res=[value/float(size)forvalueinwidth],a=np.zeros(3),p=np.zeros(3),v=np.zeros(3),b=1,d=2,m=0,big_iso=0,j=1,j2=0,j3=0,n=0,width=width,xy=xy,radius_size=size*0.5,x0=x0,y0=y0,x=np.zeros(400),y=np.zeros(400),x2=np.zeros(400),y2=np.zeros(400),x3=np.zeros(400),y3=np.zeros(400),xp1=np.zeros(800),yp1=np.zeros(800),xp2=np.zeros(400),yp2=np.zeros(400),)p.a[0]=T.strikep.a[1]=N.strikep.a[2]=P.strikep.p[0]=T.dipp.p[1]=N.dipp.p[2]=P.dipp.v[0]=T.valp.v[1]=N.valp.v[2]=P.valvi=(p.v[0]+p.v[1]+p.v[2])/3.0foriinrange(3):p.v[i]=p.v[i]-viifnp.fabs(p.v[0]*p.v[0]+p.v[1]*p.v[1]+p.v[2]*p.v[2])<EPSILON:# pure implosion-explosionifvi>0.0:cir=patches.Ellipse(xy,width=width[0],height=width[1])collect.append(cir)colors.append("b")ifvi<0.0:cir=patches.Ellipse(xy,width=width[0],height=width[1])collect.append(cir)colors.append("w")returncolors,collectifnp.fabs(p.v[0])>=np.fabs(p.v[2]):p.d=0p.m=2ifplot_zerotrace:vi=0.0f=-p.v[1]/float(p.v[p.d])iso=vi/float(p.v[p.d])return_plot(p,f,iso,collect,colors)
def_plot1(p,rgb1,colors,collect):foriinrange(p.j):p.xp1[i]=p.x[i]p.yp1[i]=p.y[i]ifp.azi[0][0]-p.azi[0][1]>np.pi:p.azi[0][0]-=np.pi*2.0elifp.azi[0][1]-p.azi[0][0]>np.pi:p.azi[0][0]+=np.pi*2.0ifp.azi[0][0]<p.azi[0][1]:az=p.azi[0][1]-D2Rwhileaz>p.azi[0][0]:si=np.sin(az)co=np.cos(az)p.xp1[i]=p.x0+p.radius_size*sip.yp1[i]=p.y0+p.radius_size*coi+=1az-=D2Relse:az=p.azi[0][1]+D2Rwhileaz<p.azi[0][0]:si=np.sin(az)co=np.cos(az)p.xp1[i]=p.x0+p.radius_size*sip.yp1[i]=p.y0+p.radius_size*coi+=1az+=D2Rcollect.append(xy2patch(p.xp1[:i],p.yp1[:i],p.res,p.xy))colors.append(rgb1)foriinrange(p.j2):p.xp2[i]=p.x2[i]p.yp2[i]=p.y2[i]ifp.azi[1][0]-p.azi[1][1]>np.pi:p.azi[1][0]-=np.pi*2.0elifp.azi[1][1]-p.azi[1][0]>np.pi:p.azi[1][0]+=np.pi*2.0ifp.azi[1][0]<p.azi[1][1]:az=p.azi[1][1]-D2Rwhileaz>p.azi[1][0]:si=np.sin(az)co=np.cos(az)p.xp2[i]=p.x0+p.radius_size*sii+=1p.yp2[i]=p.y0+p.radius_size*coaz-=D2Relse:az=p.azi[1][1]+D2Rwhileaz<p.azi[1][0]:si=np.sin(az)co=np.cos(az)p.xp2[i]=p.x0+p.radius_size*sii+=1p.yp2[i]=p.y0+p.radius_size*coaz+=D2Rcollect.append(xy2patch(p.xp2[:i],p.yp2[:i],p.res,p.xy))colors.append(rgb1)returncolors,collectdef_plot2(p,rgb1,colors,collect):foriinrange(p.j3):p.xp1[i]=p.x3[i]p.yp1[i]=p.y3[i]foriiinrange(p.j):p.xp1[i]=p.x[ii]i+=1p.yp1[i]=p.y[ii]ifp.big_iso:ii=p.j2-1whileii>=0:p.xp1[i]=p.x2[ii]i+=1p.yp1[i]=p.y2[ii]ii-=1collect.append(xy2patch(p.xp1[:i],p.yp1[:i],p.res,p.xy))colors.append(rgb1)returncolors,collectifp.azi[2][0]-p.azi[0][1]>np.pi:p.azi[2][0]-=np.pi*2.0elifp.azi[0][1]-p.azi[2][0]>np.pi:p.azi[2][0]+=np.pi*2.0ifp.azi[2][0]<p.azi[0][1]:az=p.azi[0][1]-D2Rwhileaz>p.azi[2][0]:si=np.sin(az)co=np.cos(az)p.xp1[i]=p.x0+p.radius_size*sii+=1p.yp1[i]=p.y0+p.radius_size*coaz-=D2Relse:az=p.azi[0][1]+D2Rwhileaz<p.azi[2][0]:si=np.sin(az)co=np.cos(az)p.xp1[i]=p.x0+p.radius_size*sii+=1p.yp1[i]=p.y0+p.radius_size*coaz+=D2Rcollect.append(xy2patch(p.xp1[:i],p.yp1[:i],p.res,p.xy))colors.append(rgb1)foriinrange(p.j2):p.xp2[i]=p.x2[i]p.yp2[i]=p.y2[i]ifp.azi[1][0]-p.azi[1][1]>np.pi:p.azi[1][0]-=np.pi*2.0elifp.azi[1][1]-p.azi[1][0]>np.pi:p.azi[1][0]+=np.pi*2.0ifp.azi[1][0]<p.azi[1][1]:az=p.azi[1][1]-D2Rwhileaz>p.azi[1][0]:si=np.sin(az)co=np.cos(az)p.xp2[i]=p.x0+p.radius_size*sii+=1p.yp2[i]=p.y0+p.radius_size*coaz-=D2Relse:az=p.azi[1][1]+D2Rwhileaz<p.azi[1][0]:si=np.sin(az)co=np.cos(az)p.xp2[i]=p.x0+p.radius_size*sii+=1p.yp2[i]=p.y0+p.radius_size*coaz+=D2Rcollect.append(xy2patch(p.xp2[:i],p.yp2[:i],p.res,p.xy))colors.append(rgb1)returncolors,collectdef_update_azi(p,f,iso):b,d,m=p.b,p.d,p.mspd=np.sin(p.p[d]*D2R)cpd=np.cos(p.p[d]*D2R)spb=np.sin(p.p[b]*D2R)cpb=np.cos(p.p[b]*D2R)spm=np.sin(p.p[m]*D2R)cpm=np.cos(p.p[m]*D2R)sad=np.sin(p.a[d]*D2R)cad=np.cos(p.a[d]*D2R)sab=np.sin(p.a[b]*D2R)cab=np.cos(p.a[b]*D2R)sam=np.sin(p.a[m]*D2R)cam=np.cos(p.a[m]*D2R)foriinrange(360):fir=i*D2Rs2alphan=(2.0+2.0*iso)/float(3.0+(1.0-2.0*f)*np.cos(2.0*fir))ifs2alphan>1.0:p.big_iso+=1else:alphan=np.arcsin(np.sqrt(s2alphan))sfi=np.sin(fir)cfi=np.cos(fir)san=np.sin(alphan)can=np.cos(alphan)xz=can*spd+san*sfi*spb+san*cfi*spmxn=(can*cpd*cad+san*sfi*cpb*cab+san*cfi*cpm*cam)xe=(can*cpd*sad+san*sfi*cpb*sab+san*cfi*cpm*sam)ifnp.fabs(xn)<EPSILONandnp.fabs(xe)<EPSILON:takeoff=0.0az=0.0else:az=np.arctan2(xe,xn)ifaz<0.0:az+=np.pi*2.0takeoff=np.arccos(xz/float(np.sqrt(xz*xz+xn*xn+xe*xe)))iftakeoff>np.pi/2.0:takeoff=np.pi-takeoffaz+=np.piifaz>np.pi*2.0:az-=np.pi*2.0r=np.sqrt(2)*np.sin(takeoff/2.0)si=np.sin(az)co=np.cos(az)ifi==0:p.azi[i][0]=azp.x[i]=p.x0+p.radius_size*r*sip.y[i]=p.y0+p.radius_size*r*coazp=azelse:ifnp.fabs(np.fabs(az-azp)-np.pi)<D2R*10.0:p.azi[p.n][1]=p.azpp.n+=1p.azi[p.n][0]=p.azifnp.fabs(np.fabs(az-azp)-np.pi*2.0)<D2R*2.0:ifazp<az:p.azi[p.n][0]+=np.pi*2.0else:p.azi[p.n][0]-=np.pi*2.0ifp.n==0:p.x[p.j]=p.x0+p.radius_size*r*sip.y[p.j]=p.y0+p.radius_size*r*cop.j+=1elifp.n==1:p.x2[p.j2]=p.x0+p.radius_size*r*sip.y2[p.j2]=p.y0+p.radius_size*r*cop.j2+=1elifp.n==2:p.x3[p.j3]=p.x0+p.radius_size*r*sip.y3[p.j3]=p.y0+p.radius_size*r*cop.j3+=1azp=azp.azi[p.n][1]=azdef_plot(p,f,iso,collect,colors):# Cliff Frohlich, Seismological Research letters,# Vol 7, Number 1, January-February, 1996# Unless the isotropic parameter lies in the range# between -1 and 1 - f there will be no nodes whatsoeverifiso<-1:cir=patches.Ellipse(p.xy,width=p.width[0],height=p.width[1])collect.append(cir)colors.append("w")returncolors,collectelifiso>1-f:cir=patches.Ellipse(p.xy,width=p.width[0],height=p.width[1])collect.append(cir)colors.append("b")returncolors,collect_update_azi(p,f,iso)ifp.v[1]<0.0:rgb1="b"rgb2="w"else:rgb1="w"rgb2="b"cir=patches.Ellipse(p.xy,width=p.width[0],height=p.width[1])collect.append(cir)colors.append(rgb2)ifp.n==0:collect.append(xy2patch(p.x[:360],p.y[:360],p.res,p.xy))colors.append(rgb1)returncolors,collectelifp.n==1:return_plot1(p,rgb1,colors,collect)elifp.n==2:return_plot2(p,rgb1,colors,collect)
[docs]defplotDC(np1,size=200,xy=(0,0),width=200):""" Uses one nodal plane of a double couple to draw a beach ball plot. :param ax: axis object of a matplotlib figure :param np1: :class:`~NodalPlane` Adapted from MATLAB script `bb.m <http://www.ceri.memphis.edu/people/olboyd/Software/Software.html>`_ written by Andy Michael and Oliver Boyd. """# check if one or two widths are specified (Circle or Ellipse)try:assertlen(width)==2exceptTypeError:width=(width,width)S1=np1.strikeD1=np1.dipR1=np1.rakeM=0ifR1>180:R1-=180M=1ifR1<0:R1+=180M=1# Get azimuth and dip of second plane(S2,D2,_R2)=AuxPlane(S1,D1,R1)D=size/2ifD1>=90:D1=89.9999ifD2>=90:D2=89.9999# arange checked for numerical stablility, np.pi is not multiple of 0.1phi=np.arange(np.pi,0.01)l1=np.sqrt(np.power(90-D1,2)/(np.power(np.sin(phi),2)+np.power(np.cos(phi),2)*np.power(90-D1,2)/np.power(90,2)))l2=np.sqrt(np.power(90-D2,2)/(np.power(np.sin(phi),2)+np.power(np.cos(phi),2)*np.power(90-D2,2)/np.power(90,2)))inc=1(X1,Y1)=Pol2Cart(phi+S1*D2R,l1)ifM==1:lo=S1-180hi=S2iflo>hi:inc=-1th1=np.arange(S1-180,S2,inc)(Xs1,Ys1)=Pol2Cart(th1*D2R,90*np.ones((1,len(th1))))(X2,Y2)=Pol2Cart(phi+S2*D2R,l2)th2=np.arange(S2+180,S1,-inc)else:hi=S1-180lo=S2-180iflo>hi:inc=-1th1=np.arange(hi,lo,-inc)(Xs1,Ys1)=Pol2Cart(th1*D2R,90*np.ones((1,len(th1))))(X2,Y2)=Pol2Cart(phi+S2*D2R,l2)X2=X2[::-1]Y2=Y2[::-1]th2=np.arange(S2,S1,inc)(Xs2,Ys2)=Pol2Cart(th2*D2R,90*np.ones((1,len(th2))))X=np.concatenate((X1,Xs1[0],X2,Xs2[0]))Y=np.concatenate((Y1,Ys1[0],Y2,Ys2[0]))X=X*D/90Y=Y*D/90# calculate resolutionres=[value/float(size)forvalueinwidth]# construct the patchescollect=[patches.Ellipse(xy,width=width[0],height=width[1])]collect.append(xy2patch(Y,X,res,xy))return["b","w"],collect
[docs]defxy2patch(x,y,res,xy):# check if one or two resolutions are specified (Circle or Ellipse)try:assertlen(res)==2exceptTypeError:res=(res,res)# transform into the Path coordinate systemx=x*res[0]+xy[0]y=y*res[1]+xy[1]verts=list(zip(x.tolist(),y.tolist()))codes=[mplpath.Path.MOVETO]codes.extend([mplpath.Path.LINETO]*(len(x)-2))codes.append(mplpath.Path.CLOSEPOLY)path=mplpath.Path(verts,codes)returnpatches.PathPatch(path)
[docs]defStrikeDip(n,e,u):""" Finds strike and dip of plane given normal vector having components n, e, and u. Adapted from MATLAB script `bb.m <http://www.ceri.memphis.edu/people/olboyd/Software/Software.html>`_ written by Andy Michael and Oliver Boyd. """r2d=180/np.piifu<0:n=-ne=-eu=-ustrike=np.arctan2(e,n)*r2dstrike=strike-90whilestrike>=360:strike=strike-360whilestrike<0:strike=strike+360x=np.sqrt(np.power(n,2)+np.power(e,2))dip=np.arctan2(x,u)*r2dreturn(strike,dip)
[docs]defAuxPlane(s1,d1,r1):""" Get Strike and dip of second plane. Adapted from MATLAB script `bb.m <http://www.ceri.memphis.edu/people/olboyd/Software/Software.html>`_ written by Andy Michael and Oliver Boyd. """r2d=180/np.piz=(s1+90)/r2dz2=d1/r2dz3=r1/r2d# slick vector in plane 1sl1=-np.cos(z3)*np.cos(z)-np.sin(z3)*np.sin(z)*np.cos(z2)sl2=np.cos(z3)*np.sin(z)-np.sin(z3)*np.cos(z)*np.cos(z2)sl3=np.sin(z3)*np.sin(z2)(strike,dip)=StrikeDip(sl2,sl1,sl3)n1=np.sin(z)*np.sin(z2)# normal vector to plane 1n2=np.cos(z)*np.sin(z2)h1=-sl2# strike vector of plane 2h2=sl1# note h3=0 always so we leave it out# n3 = np.cos(z2)z=h1*n1+h2*n2z=z/np.sqrt(h1*h1+h2*h2)z=np.arccos(z)rake=0ifsl3>0:rake=z*r2difsl3<=0:rake=-z*r2dreturn(strike,dip,rake)
[docs]defMT2Plane(mt):""" Calculates a nodal plane of a given moment tensor. :param mt: :class:`~MomentTensor` :return: :class:`~NodalPlane` Adapted from MATLAB script `bb.m <http://www.ceri.memphis.edu/people/olboyd/Software/Software.html>`_ written by Andy Michael and Oliver Boyd. """(d,v)=np.linalg.eig(mt.mt)D=np.array([d[1],d[0],d[2]])V=np.array([[v[1,1],-v[1,0],-v[1,2]],[v[2,1],-v[2,0],-v[2,2]],[-v[0,1],v[0,0],v[0,2]],])IMAX=D.argmax()IMIN=D.argmin()AE=(V[:,IMAX]+V[:,IMIN])/np.sqrt(2.0)AN=(V[:,IMAX]-V[:,IMIN])/np.sqrt(2.0)AER=np.sqrt(np.power(AE[0],2)+np.power(AE[1],2)+np.power(AE[2],2))ANR=np.sqrt(np.power(AN[0],2)+np.power(AN[1],2)+np.power(AN[2],2))AE=AE/AERifnotANR:AN=np.array([np.nan,np.nan,np.nan])else:AN=AN/ANRifAN[2]<=0.0:AN1=ANAE1=AEelse:AN1=-ANAE1=-AE(ft,fd,fl)=TDL(AN1,AE1)returnNodalPlane(360-ft,fd,180-fl)
[docs]defTDL(AN,BN):""" Helper function for MT2Plane. Adapted from MATLAB script `bb.m <http://www.ceri.memphis.edu/people/olboyd/Software/Software.html>`_ written by Andy Michael and Oliver Boyd. """XN=AN[0]YN=AN[1]ZN=AN[2]XE=BN[0]YE=BN[1]ZE=BN[2]AAA=1.0/(1000000)CON=57.2957795ifnp.fabs(ZN)<AAA:FD=90.0AXN=np.fabs(XN)ifAXN>1.0:AXN=1.0FT=np.arcsin(AXN)*CONST=-XNCT=YNifST>=0.0andCT<0:FT=180.0-FTifST<0.0andCT<=0:FT=180.0+FTifST<0.0andCT>0:FT=360.0-FTFL=np.arcsin(abs(ZE))*CONSL=-ZEifnp.fabs(XN)<AAA:CL=XE/YNelse:CL=-YE/XNifSL>=0.0andCL<0:FL=180.0-FLifSL<0.0andCL<=0:FL=FL-180.0ifSL<0.0andCL>0:FL=-FLelse:if-ZN>1.0:ZN=-1.0FDH=np.arccos(-ZN)FD=FDH*CONSD=np.sin(FDH)ifSD==0:returnST=-XN/SDCT=YN/SDSX=np.fabs(ST)ifSX>1.0:SX=1.0FT=np.arcsin(SX)*CONifST>=0.0andCT<0:FT=180.0-FTifST<0.0andCT<=0:FT=180.0+FTifST<0.0andCT>0:FT=360.0-FTSL=-ZE/SDSX=np.fabs(SL)ifSX>1.0:SX=1.0FL=np.arcsin(SX)*CONifST==0:CL=XE/CTelse:XXX=YN*ZN*ZE/SD/SD+YECL=-SD*XXX/XNifCT==0:CL=YE/STifSL>=0.0andCL<0:FL=180.0-FLifSL<0.0andCL<=0:FL=FL-180.0ifSL<0.0andCL>0:FL=-FLreturn(FT,FD,FL)
[docs]defMT2Axes(mt):""" Calculates the principal axes of a given moment tensor. :param mt: :class:`~MomentTensor` :return: tuple of :class:`~PrincipalAxis` T, N and P Adapted from ps_tensor / utilmeca.c / `Generic Mapping Tools (GMT) <http://gmt.soest.hawaii.edu>`_. """(D,V)=np.linalg.eigh(mt.mt)pl=np.arcsin(-V[0])az=np.arctan2(V[2],-V[1])foriinrange(3):ifpl[i]<=0:pl[i]=-pl[i]az[i]+=np.piifaz[i]<0:az[i]+=2*np.piifaz[i]>2*np.pi:az[i]-=2*np.pipl*=R2Daz*=R2DT=PrincipalAxis(D[2],az[2],pl[2])N=PrincipalAxis(D[1],az[1],pl[1])P=PrincipalAxis(D[0],az[0],pl[0])return(T,N,P)
[docs]classPrincipalAxis(object):""" A principal axis. Strike and dip values are in degrees. >>> a = PrincipalAxis(1.3, 20, 50) >>> a.dip 50 >>> a.strike 20 >>> a.val 1.3 """def__init__(self,val=0,strike=0,dip=0):self.val=valself.strike=strikeself.dip=dip
[docs]classNodalPlane(object):""" A nodal plane. All values are in degrees. >>> a = NodalPlane(13, 20, 50) >>> a.strike 13 >>> a.dip 20 >>> a.rake 50 """def__init__(self,strike=0,dip=0,rake=0):self.strike=strikeself.dip=dipself.rake=rake
[docs]classMomentTensor(object):""" A moment tensor. >>> a = MomentTensor(1, 1, 0, 0, 0, -1, 26) >>> b = MomentTensor(np.array([1, 1, 0, 0, 0, -1]), 26) >>> c = MomentTensor(np.array([[1, 0, 0], [0, 1, -1], [0, -1, 0]]), 26) >>> a.mt array([[ 1, 0, 0], [ 0, 1, -1], [ 0, -1, 0]]) >>> b.yz -1 >>> a.expo 26 """def__init__(self,*args):iflen(args)==2:A=args[0]self.expo=args[1]iflen(A)==6:# six independent componentsself.mt=np.array([[A[0],A[3],A[4]],[A[3],A[1],A[5]],[A[4],A[5],A[2]],])elifisinstance(A,np.ndarray)andA.shape==(3,3):# full matrixself.mt=Aelse:raiseTypeError("Wrong size of input parameter.")eliflen(args)==7:# six independent componentsself.mt=np.array([[args[0],args[3],args[4]],[args[3],args[1],args[5]],[args[4],args[5],args[2]],])self.expo=args[6]else:raiseTypeError("Wrong size of input parameter.")@propertydefxx(self):returnself.mt[0][0]@propertydefxy(self):returnself.mt[0][1]@propertydefxz(self):returnself.mt[0][2]@propertydefyz(self):returnself.mt[1][2]@propertydefyy(self):returnself.mt[1][1]@propertydefzz(self):returnself.mt[2][2]