Source code for openquake.hazardlib.source.rupture
# coding: utf-8# The Hazard Library# Copyright (C) 2012-2023 GEM Foundation## This program 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.## This program 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 this program. If not, see <http://www.gnu.org/licenses/>."""Module :mod:`openquake.hazardlib.source.rupture` defines classes:class:`BaseRupture` and its subclasses:class:`NonParametricProbabilisticRupture` and:class:`ParametricProbabilisticRupture`"""importabcimportnumpyimportmathimportitertoolsimportjsonfromopenquake.baselibimportgeneral,hdf5fromopenquake.hazardlibimportgeofromopenquake.hazardlib.geo.nodalplaneimportNodalPlanefromopenquake.hazardlib.geo.meshimport(Mesh,RectangularMesh,surface_to_arrays)fromopenquake.hazardlib.geo.pointimportPointfromopenquake.hazardlib.geo.geodeticimportgeodetic_distancefromopenquake.hazardlib.near_faultimport(get_plane_equation,projection_pp,directp,average_s_rad,isochone_ratio)fromopenquake.hazardlib.geo.surfaceimportBaseSurface,PlanarSurfaceU8=numpy.uint8U16=numpy.uint16U32=numpy.uint32I64=numpy.int64F32=numpy.float32F64=numpy.float64TWO16=2**16TWO24=2**24TWO30=2**30TWO32=2**32pmf_dt=numpy.dtype([('prob',float),('occ',U32)])events_dt=numpy.dtype([('id',U32),('rup_id',I64),('rlz_id',U16)])rup_dt=numpy.dtype([('seed',U32),('mag',F32),('rake',F32),('lon',F32),('lat',F32),('dep',F32),('multiplicity',U32),('trt',hdf5.vstr),('kind',hdf5.vstr),('mesh',hdf5.vstr),('extra',hdf5.vstr)])rupture_dt=numpy.dtype([('id',I64),('seed',I64),('source_id',U32),('trt_smr',U32),('code',U8),('n_occ',U32),('mag',F32),('rake',F32),('occurrence_rate',F32),('minlon',F32),('minlat',F32),('maxlon',F32),('maxlat',F32),('hypo',(F32,3)),('geom_id',U32),('nsites',U32),('e0',U32)])code2cls={}
[docs]defto_csv_array(ebruptures):""" :param ebruptures: a list of EBRuptures :returns: an array suitable for serialization in CSV """ifnotcode2cls:code2cls.update(BaseRupture.init())arr=numpy.zeros(len(ebruptures),rup_dt)forrec,ebrinzip(arr,ebruptures):rup=ebr.rupture# s0=number of multi surfaces, s1=number of rows, s2=number of columnsarrays=surface_to_arrays(rup.surface)# shape (s0, 3, s1, s2)rec['seed']=ebr.seedrec['mag']=rup.magrec['rake']=rup.rakerec['lon']=rup.hypocenter.xrec['lat']=rup.hypocenter.yrec['dep']=rup.hypocenter.zrec['multiplicity']=rup.multiplicityrec['trt']=rup.tectonic_region_typerec['kind']=' '.join(cls.__name__forclsincode2cls[rup.code])rec['mesh']=json.dumps([[[[float5(z)forziny]foryinx]forxinarray]forarrayinarrays])extra={}ifhasattr(rup,'probs_occur'):extra['probs_occur']=rup.probs_occurelse:extra['occurrence_rate']=rup.occurrence_rateifhasattr(rup,'weight'):extra['weight']=rup.weight_fixfloat32(extra)rec['extra']=json.dumps(extra)returnarr
[docs]defto_arrays(geom):""" :param geom: an array [num_surfaces, shape_y, shape_z ..., coords] :returns: a list of num_surfaces arrays with shape (3, shape_y, shape_z) """arrays=[]num_surfaces=int(geom[0])start=num_surfaces*2+1foriinrange(1,2*num_surfaces,2):s1,s2=int(geom[i]),int(geom[i+1])size=s1*s2*3array=geom[start:start+size].reshape(3,s1,s2)arrays.append(array)start+=sizereturnarrays
[docs]defget_ebr(rec,geom,trt):""" Convert a rupture record plus geometry into an EBRupture instance """# rec: a dictionary or a record# geom: if any, an array of floats32 convertible into a mesh# NB: not implemented: rupture_slip_directionifnotcode2cls:code2cls.update(BaseRupture.init())# build surfacearrays=to_arrays(geom)mesh=arrays[0]rupture_cls,surface_cls=code2cls[rec['code']]surface=object.__new__(surface_cls)ifsurface_clsisgeo.PlanarSurface:surface=geo.PlanarSurface.from_array(mesh[:,0,:])elifsurface_clsisgeo.MultiSurface:ifall(array.shape==(3,1,4)forarrayinarrays):# for PlanarSurfaces each array has shape (3, 1, 4)surface.__init__([geo.PlanarSurface.from_array(array[:,0,:])forarrayinarrays])else:# assume KiteSurfacessurface.__init__([geo.KiteSurface(RectangularMesh(*array))forarrayinarrays])elifsurface_clsisgeo.GriddedSurface:# fault surface, strike and dip will be computedsurface.strike=surface.dip=Nonesurface.mesh=Mesh(*mesh)else:# fault surface, strike and dip will be computedsurface.strike=surface.dip=Nonesurface.__init__(RectangularMesh(*mesh))# build rupturerupture=object.__new__(rupture_cls)rupture.seed=rec['seed']rupture.surface=surfacerupture.mag=rec['mag']rupture.rake=rec['rake']rupture.hypocenter=geo.Point(*rec['hypo'])rupture.occurrence_rate=rec['occurrence_rate']try:rupture.probs_occur=rec['probs_occur']except(KeyError,ValueError):# rec can be a numpy recordpassrupture.tectonic_region_type=trtrupture.multiplicity=rec['n_occ']# build EBRuptureebr=EBRupture(rupture,rec['source_id'],rec['trt_smr'],rec['n_occ'],rec['id']%TWO30,rec['e0'])ebr.seed=rec['seed']returnebr
[docs]deffloat5(x):# a float with 5 digitsreturnround(float(x),5)
def_fixfloat32(dic):# work around a TOML/numpy issuefork,vindic.items():ifisinstance(v,F32):dic[k]=float5(v)elifisinstance(v,tuple):dic[k]=[float5(x)forxinv]elifisinstance(v,numpy.ndarray):iflen(v.shape)==3:# 3D arraydic[k]=[[[float5(z)forziny]foryinx]forxinv]eliflen(v.shape)==2:# 2D arraydic[k]=[[float5(y)foryinx]forxinv]eliflen(v.shape)==1:# 1D arraydic[k]=[float5(x)forxinv]else:raiseNotImplementedError
[docs]defto_checksum8(cls1,cls2):""" Convert a pair of classes into a numeric code (uint8) """names='%s,%s'%(cls1.__name__,cls2.__name__)returnsum(map(ord,names))%256
[docs]classBaseRupture(metaclass=abc.ABCMeta):""" Rupture object represents a single earthquake rupture. :param mag: Magnitude of the rupture. :param rake: Rake value of the rupture. See :class:`~openquake.hazardlib.geo.nodalplane.NodalPlane`. :param tectonic_region_type: Rupture's tectonic regime. One of constants in :class:`openquake.hazardlib.const.TRT`. :param hypocenter: A :class:`~openquake.hazardlib.geo.point.Point`, rupture's hypocenter. :param surface: An instance of subclass of :class:`~openquake.hazardlib.geo.surface.base.BaseSurface`. Object representing the rupture surface geometry. :param rupture_slip_direction: Angle describing rupture propagation direction in decimal degrees. :raises ValueError: If magnitude value is not positive, or tectonic region type is unknown. NB: if you want to convert the rupture into XML, you should set the attribute surface_nodes to an appropriate value. """_code={}
[docs]@classmethoddefinit(cls):""" Initialize the class dictionary `._code` by encoding the bidirectional correspondence between an integer in the range 0..255 (the code) and a pair of classes (rupture_class, surface_class). This is useful when serializing the rupture to and from HDF5. :returns: {code: pair of classes} """rupture_classes=[BaseRupture]+list(general.gen_subclasses(BaseRupture))surface_classes=list(general.gen_subclasses(BaseSurface))code2cls={}BaseRupture.str2code={}forrup,surinitertools.product(rupture_classes,surface_classes):chk=to_checksum8(rup,sur)ifchkincode2clsandcode2cls[chk]!=(rup,sur):raiseValueError('Non-unique checksum %d for %s, %s'%(chk,rup,sur))cls._code[rup,sur]=chkcode2cls[chk]=rup,surBaseRupture.str2code['%s%s'%(rup.__name__,sur.__name__)]=chkreturncode2cls
def__init__(self,mag,rake,tectonic_region_type,hypocenter,surface,rupture_slip_direction=None,weight=None):ifnotmag>0:raiseValueError('magnitude must be positive')NodalPlane.check_rake(rake)self.tectonic_region_type=tectonic_region_typeself.rake=rakeself.mag=magself.hypocenter=hypocenterself.surface=surfaceself.rupture_slip_direction=rupture_slip_directionself.ruid=None@propertydefhypo_depth(self):"""Returns the hypocentral depth"""returnself.hypocenter.z@propertydefcode(self):"""Returns the code (integer in the range 0 .. 255) of the rupture"""returnself._code[self.__class__,self.surface.__class__]
[docs]defsize(self):""" Dummy method for compatibility with the RuptureContext. :returns: 1 """return1
[docs]defsample_number_of_occurrences(self,n,rng):""" Randomly sample number of occurrences from temporal occurrence model probability distribution. .. note:: This method is using random numbers. In order to reproduce the same results numpy random numbers generator needs to be seeded, see http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.seed.html :returns: numpy array of size n with number of rupture occurrences """raiseNotImplementedError
[docs]classNonParametricProbabilisticRupture(BaseRupture):""" Probabilistic rupture for which the probability distribution for rupture occurrence is described through a generic probability mass function. :param pmf: Instance of :class:`openquake.hazardlib.pmf.PMF`. Values in the abscissae represent number of rupture occurrences (in increasing order, staring from 0) and values in the ordinates represent associated probabilities. Example: if, for a given time span, a rupture has probability ``0.8`` to not occurr, ``0.15`` to occur once, and ``0.05`` to occur twice, the ``pmf`` can be defined as :: pmf = PMF([(0.8, 0), (0.15, 1), 0.05, 2)]) :raises ValueError: If number of ruptures in ``pmf`` do not start from 0, are not defined in increasing order, and if they are not defined with unit step """def__init__(self,mag,rake,tectonic_region_type,hypocenter,surface,pmf,rupture_slip_direction=None,weight=None):occ=numpy.array([occfor(prob,occ)inpmf.data])ifnotocc[0]==0:raiseValueError('minimum number of ruptures must be zero')ifnotnumpy.all(numpy.sort(occ)==occ):raiseValueError('numbers of ruptures must be defined in increasing order')ifnotnumpy.all(numpy.diff(occ)==1):raiseValueError('numbers of ruptures must be defined with unit step')super().__init__(mag,rake,tectonic_region_type,hypocenter,surface,rupture_slip_direction,weight)# an array of probabilities with sum 1self.probs_occur=numpy.array([probfor(prob,occ)inpmf.data])ifweightisnotNone:self.weight=weight
[docs]defsample_number_of_occurrences(self,n,rng):""" See :meth:`superclass method <.rupture.BaseRupture.sample_number_of_occurrences>` for spec of input and result values. Uses 'Inverse Transform Sampling' method. """# compute cdf from pmfcdf=numpy.cumsum(self.probs_occur)n_occ=numpy.digitize(rng.random(n),cdf)returnn_occ
[docs]classParametricProbabilisticRupture(BaseRupture):""" :class:`Rupture` associated with an occurrence rate and a temporal occurrence model. :param occurrence_rate: Number of times rupture happens per year. :param temporal_occurrence_model: Temporal occurrence model assigned for this rupture. Should be an instance of :class:`openquake.hazardlib.tom.PoissonTOM`. :raises ValueError: If occurrence rate is not positive. """def__init__(self,mag,rake,tectonic_region_type,hypocenter,surface,occurrence_rate,temporal_occurrence_model,rupture_slip_direction=None):ifnotoccurrence_rate>0:raiseValueError('occurrence rate must be positive')super().__init__(mag,rake,tectonic_region_type,hypocenter,surface,rupture_slip_direction)self.temporal_occurrence_model=temporal_occurrence_modelself.occurrence_rate=occurrence_rate
[docs]defget_probability_one_or_more_occurrences(self):""" Return the probability of this rupture to occur one or more times. Uses :meth:`~openquake.hazardlib.tom.PoissonTOM.get_probability_one_or_more_occurrences` of an assigned temporal occurrence model. """tom=self.temporal_occurrence_modelrate=self.occurrence_ratereturntom.get_probability_one_or_more_occurrences(rate)
[docs]defget_probability_one_occurrence(self):""" Return the probability of this rupture to occur exactly one time. Uses :meth: `openquake.hazardlib.tom.PoissonTOM.get_probability_n_occurrences` of an assigned temporal occurrence model. """tom=self.temporal_occurrence_modelrate=self.occurrence_ratereturntom.get_probability_n_occurrences(rate,1)
[docs]defsample_number_of_occurrences(self,n,rng):""" Draw a random sample from the distribution and return a number of events to occur as an array of integers of size n. Uses :meth: `openquake.hazardlib.tom.PoissonTOM.sample_number_of_occurrences` of an assigned temporal occurrence model. """r=self.occurrence_rate*self.temporal_occurrence_model.time_spanreturnrng.poisson(r,n)
[docs]defget_dppvalue(self,site):""" Get the directivity prediction value, DPP at a given site as described in Spudich et al. (2013). :param site: :class:`~openquake.hazardlib.geo.point.Point` object representing the location of the target site :returns: A float number, directivity prediction value (DPP). """origin=self.surface.get_resampled_top_edge()[0]dpp_multi=[]index_patch=self.surface.hypocentre_patch_index(self.hypocenter,self.surface.get_resampled_top_edge(),self.surface.mesh.depths[0][0],self.surface.mesh.depths[-1][0],self.surface.get_dip())idx_nxtp=Truehypocenter=self.hypocenterwhileidx_nxtp:# E Plane Calculationp0,p1,p2,p3=self.surface.get_fault_patch_vertices(self.surface.get_resampled_top_edge(),self.surface.mesh.depths[0][0],self.surface.mesh.depths[-1][0],self.surface.get_dip(),index_patch=index_patch)[normal,dist_to_plane]=get_plane_equation(p0,p1,p2,origin)pp=projection_pp(site,normal,dist_to_plane,origin)pd,e,idx_nxtp=directp(p0,p1,p2,p3,hypocenter,origin,pp)pd_geo=origin.point_at((pd[0]**2+pd[1]**2)**0.5,-pd[2],numpy.degrees(math.atan2(pd[0],pd[1])))# determine the lower bound of E path valuef1=geodetic_distance(p0.longitude,p0.latitude,p1.longitude,p1.latitude)f2=geodetic_distance(p2.longitude,p2.latitude,p3.longitude,p3.latitude)iff1>f2:f=f1else:f=f2fs,rd,r_hyp=average_s_rad(site,hypocenter,origin,pp,normal,dist_to_plane,e,p0,p1,self.rupture_slip_direction)cprime=isochone_ratio(e,rd,r_hyp)dpp_exp=cprime*numpy.maximum(e,0.1*f)*\
numpy.maximum(fs,0.2)dpp_multi.append(dpp_exp)# check if go through the next patch of the faultindex_patch=index_patch+1if(len(self.surface.get_resampled_top_edge())<=2)and(index_patch>=len(self.surface.get_resampled_top_edge())):idx_nxtp=Falseelifindex_patch>=len(self.surface.get_resampled_top_edge()):idx_nxtp=Falseelifidx_nxtp:hypocenter=pd_geoidx_nxtp=True# calculate DPP value of the site.dpp=numpy.log(numpy.sum(dpp_multi))returndpp
[docs]defget_cdppvalue(self,target,buf=1.0,delta=0.01,space=2.):""" Get the directivity prediction value, centered DPP(cdpp) at a given site as described in Spudich et al. (2013), and this cdpp is used in Chiou and Young (2014) GMPE for near-fault directivity term prediction. :param target_site: A mesh object representing the location of the target sites. :param buf: A buffer distance in km to extend the mesh borders :param delta: The distance between two adjacent points in the mesh :param space: The tolerance for the distance of the sites (default 2 km) :returns: The centered directivity prediction value of Chiou and Young """min_lon,max_lon,max_lat,min_lat=self.surface.get_bounding_box()min_lon-=bufmax_lon+=bufmin_lat-=bufmax_lat+=buflons=numpy.arange(min_lon,max_lon+delta,delta)# ex shape (233,)lats=numpy.arange(min_lat,max_lat+delta,delta)# ex shape (204,)mesh=RectangularMesh(*numpy.meshgrid(lons,lats))mesh_rup=self.surface.get_min_distance(mesh).reshape(mesh.shape)# ex shape (204, 233)target_rup=self.surface.get_min_distance(target)# ex shape (2,)cdpp=numpy.zeros_like(target.lons)fori,(target_lon,target_lat)inenumerate(zip(target.lons,target.lats)):# indices around target_rup[i]around=(mesh_rup<=target_rup[i]+space)&(mesh_rup>=target_rup[i]-space)dpp_target=self.get_dppvalue(Point(target_lon,target_lat))dpp_mean=numpy.mean([self.get_dppvalue(Point(lon,lat))forlon,latinzip(mesh.lons[around],mesh.lats[around])])cdpp[i]=dpp_target-dpp_meanreturncdpp
[docs]classPointSurface:""" A fake surface used in PointRuptures. The parameters `hypocenter` and `strike` are determined by collapsing the corresponding parameters in the original PointSource. """def__init__(self,hypocenter,strike,dip):self.hypocenter=hypocenterself.strike=strikeself.dip=dip
[docs]defget_closest_points(self,mesh):""" :returns: N times the hypocenter if N is the number of points """N=len(mesh)lons=numpy.full(N,self.hypocenter.x)lats=numpy.full(N,self.hypocenter.y)deps=numpy.full(N,self.hypocenter.z)returnMesh(lons,lats,deps)
[docs]defget_min_distance(self,mesh):""" :returns: the distance from the hypocenter to the mesh """returnself.hypocenter.distance_to_mesh(mesh).min()
def__bool__(self):returnFalse
[docs]classPointRupture(ParametricProbabilisticRupture):""" A rupture coming from a far away PointSource, so that the finite size effects can be neglected. """def__init__(self,mag,rake,tectonic_region_type,hypocenter,strike,dip,occurrence_rate,temporal_occurrence_model,zbot):self.tectonic_region_type=tectonic_region_typeself.hypocenter=hypocenterself.mag=magself.strike=strikeself.rake=rakeself.dip=dipself.occurrence_rate=occurrence_rateself.temporal_occurrence_model=temporal_occurrence_modelself.surface=PointSurface(hypocenter,strike,dip)self.zbot=zbot# bottom edge depth, used in Campbell-Bozorgnia
[docs]defget_geom(surface,is_from_fault_source,is_multi_surface,is_gridded_surface):""" The following fields can be interpreted different ways, depending on the value of `is_from_fault_source`. If `is_from_fault_source` is True, each of these fields should contain a 2D numpy array (all of the same shape). Each triple of (lon, lat, depth) for a given index represents the node of a rectangular mesh. If `is_from_fault_source` is False, each of these fields should contain a sequence (tuple, list, or numpy array, for example) of 4 values. In order, the triples of (lon, lat, depth) represent top left, top right, bottom left, and bottom right corners of the the rupture's planar surface. Update: There is now a third case. If the rupture originated from a characteristic fault source with a multi-planar-surface geometry, `lons`, `lats`, and `depths` will contain one or more sets of 4 points, similar to how planar surface geometry is stored (see above). :param surface: a Surface instance :param is_from_fault_source: a boolean :param is_multi_surface: a boolean """ifis_from_fault_source:# for simple and complex fault sources,# rupture surface geometry is represented by a meshsurf_mesh=surface.meshlons=surf_mesh.lonslats=surf_mesh.latsdepths=surf_mesh.depthselse:ifis_multi_surface:# `list` of# openquake.hazardlib.geo.surface.planar.PlanarSurface# objects:surfaces=surface.surfaces# lons, lats, and depths are arrays with len == 4*N,# where N is the number of surfaces in the# multisurface for each `corner_*`, the ordering is:# - top left# - top right# - bottom left# - bottom rightlons=numpy.concatenate([x.corner_lonsforxinsurfaces])lats=numpy.concatenate([x.corner_latsforxinsurfaces])depths=numpy.concatenate([x.corner_depthsforxinsurfaces])elifis_gridded_surface:# the surface mesh has shape (1, N)lons=surface.mesh.lons[0]lats=surface.mesh.lats[0]depths=surface.mesh.depths[0]else:# For area or point source,# rupture geometry is represented by a planar surface,# defined by 3D corner pointslons=numpy.zeros((4))lats=numpy.zeros((4))depths=numpy.zeros((4))# NOTE: It is important to maintain the order of these# corner points. TODO: check the orderingfori,cornerinenumerate((surface.top_left,surface.top_right,surface.bottom_left,surface.bottom_right)):lons[i]=corner.longitudelats[i]=corner.latitudedepths[i]=corner.depthreturnlons,lats,depths
[docs]classExportedRupture(object):""" Simplified Rupture class with attributes rupid, events_by_ses, indices and others, used in export. :param rupid: rupture.seed ID :param events_by_ses: dictionary ses_idx -> event records :param indices: site indices """def__init__(self,rupid,n_occ,events_by_ses,indices=None):self.rupid=rupidself.n_occ=n_occself.events_by_ses=events_by_sesself.indices=indices
[docs]classEBRupture(object):""" An event based rupture. It is a wrapper over a hazardlib rupture object. :param rupture: the underlying rupture :param str source_id: ID of the source that generated the rupture :param int trt_smr: an integer describing TRT and source model realization :param int n_occ: number of occurrences of the rupture :param int e0: initial event ID (default 0) :param bool scenario: True for scenario ruptures, default False """seed='NA'# set by the enginedef__init__(self,rupture,source_id,trt_smr,n_occ=1,id=None,e0=0):self.rupture=ruptureself.source_id=source_idself.trt_smr=trt_smrself.n_occ=n_occself.id=source_id*TWO30+idself.e0=e0@propertydeftectonic_region_type(self):returnself.rupture.tectonic_region_type
[docs]defget_eids(self):""" :returns: an array of event IDs """returnnumpy.arange(self.n_occ,dtype=U32)
[docs]defget_eid_rlz(rec,rlzs,scenario):""" :param rlzs: an array of realization indices :param scenario: if true distribute the rlzs evenly else randomly :returns: two arrays (eid, rlz) """e0=rec['e0']n=rec['n_occ']eid=numpy.arange(e0,e0+n,dtype=U32)ifscenario:# the rlzs are distributed evenlyrlz=rlzs[numpy.arange(rec['n_occ'])//(n//len(rlzs))]else:# event_based: the rlzs are distributed randomlyrlz=general.random_choice(rlzs,n,0,rec['seed'])returneid,rlz
[docs]defget_events(recs,rlzs,scenario):""" Build the associations event_id -> rlz_id for each rup_id. :returns: a structured array with fields ('id', 'rup_id', 'rlz_id') """n_occ=sum(rec['n_occ']forrecinrecs)out=numpy.zeros(n_occ,events_dt)start=0forrecinrecs:n=rec['n_occ']stop=start+nslc=out[start:stop]eid,rlz=get_eid_rlz(rec,rlzs,scenario)slc['id']=eidslc['rlz_id']=rlzslc['rup_id']=rec['id']start=stopreturnout
[docs]classRuptureProxy(object):""" A proxy for a rupture record. :param rec: a record with the rupture parameters """def__init__(self,rec):self.rec=recdef__getitem__(self,name):returnself.rec[name]# NB: requires the .geom attribute to be set
[docs]defto_ebr(self,trt):""" :returns: EBRupture instance associated to the underlying rupture """returnget_ebr(self.rec,self.geom,trt)
[docs]defget_ruptures(fname_csv):""" Read ruptures in CSV format and return an ArrayWrapper. :param fname_csv: path to the CSV file """ifnotBaseRupture._code:BaseRupture.init()# initialize rupture codescode=BaseRupture.str2codeaw=hdf5.read_csv(fname_csv,{n:rup_dt[n]forninrup_dt.names})rups=[]geoms=[]n_occ=1foru,rowinenumerate(aw.array):hypo=row['lon'],row['lat'],row['dep']dic=json.loads(row['extra'])meshes=[F32(m)forminjson.loads(row['mesh'])]# 3D arraysnum_surfaces=len(meshes)shapes=[]points=[]minlons=[]maxlons=[]minlats=[]maxlats=[]formeshinmeshes:shapes.extend(mesh.shape[1:])points.extend(mesh.flatten())# lons + lats + depsminlons.append(mesh[0].min())minlats.append(mesh[1].min())maxlons.append(mesh[0].max())maxlats.append(mesh[1].max())rec=numpy.zeros(1,rupture_dt)[0]rec['seed']=row['seed']rec['minlon']=minlon=min(minlons)rec['minlat']=minlat=min(minlats)rec['maxlon']=maxlon=max(maxlons)rec['maxlat']=maxlat=max(maxlats)rec['mag']=row['mag']rec['hypo']=hyporate=dic.get('occurrence_rate',numpy.nan)trt_smr=aw.trts.index(row['trt'])*TWO24tup=(u,row['seed'],0,trt_smr,code[row['kind']],n_occ,row['mag'],row['rake'],rate,minlon,minlat,maxlon,maxlat,hypo,u,1,0)rups.append(tup)geoms.append(numpy.concatenate([[num_surfaces],shapes,points]))ifnotrups:return()dic=dict(geom=numpy.array(geoms,object),trts=aw.trts)# NB: PMFs for nonparametric ruptures are missingreturnhdf5.ArrayWrapper(numpy.array(rups,rupture_dt),dic)
[docs]defget_planar(site,msr,mag,aratio,strike,dip,rake,trt,ztor=None):""" :returns: a BaseRupture with a PlanarSurface built around the site """hc=site.locationsurf=PlanarSurface.from_hypocenter(hc,msr,mag,aratio,strike,dip,rake,ztor)rup=BaseRupture(mag,rake,trt,hc,surf)rup.rup_id=0vars(rup).update(vars(site))returnrup
[docs]defbuild_planar(hypocenter,mag,rake,strike=0.,dip=90.,trt='*'):""" Build a rupture with a PlanarSurface suitable for scenario calculations """# copying the algorithm used in PlanarSurface.from_hypocenter# with a fixed Magnitude-Scaling Relationshiprdip=math.radians(dip)rup_width,rup_length=_width_length(mag,rake)# calculate the height of the rupture being projected# on the vertical plane:rup_proj_height=rup_width*math.sin(rdip)# and its width being projected on the horizontal one:rup_proj_width=rup_width*math.cos(rdip)# half height of the vertical component of rupture width# is the vertical distance between the rupture geometrical# center and it's upper and lower borders:hheight=rup_proj_height/2.# calculate how much shallower the upper border of the rupture# is than the upper seismogenic depth:vshift=hheight-hypocenter.depth# if it is shallower (vshift > 0) than we need to move the rupture# by that value vertically.rupture_center=hypocenterifvshift>0:# we need to move the rupture center to make the rupture plane# lie below the surfacehshift=abs(vshift/math.tan(rdip))rupture_center=hypocenter.point_at(hshift,vshift,azimuth=(strike+90)%360)theta=math.degrees(math.atan((rup_proj_width/2.)/(rup_length/2.)))hor_dist=math.sqrt((rup_length/2.)**2+(rup_proj_width/2.)**2)vertical_increment=rup_proj_height/2.top_left=rupture_center.point_at(hor_dist,-vertical_increment,azimuth=(strike+180+theta)%360)top_right=rupture_center.point_at(hor_dist,-vertical_increment,azimuth=(strike-theta)%360)bottom_left=rupture_center.point_at(hor_dist,vertical_increment,azimuth=(strike+180-theta)%360)bottom_right=rupture_center.point_at(hor_dist,vertical_increment,azimuth=(strike+theta)%360)surf=PlanarSurface(strike,dip,top_left,top_right,bottom_right,bottom_left)rup=BaseRupture(mag,rake,trt,hypocenter,surf)rup.rup_id=0vars(rup).update(vars(hypocenter))returnrup