Source code for openquake.hazardlib.sourceconverter
# -*- coding: utf-8 -*-# vim: tabstop=4 shiftwidth=4 softtabstop=4## Copyright (C) 2015-2023 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/>.importosimportoperatorimportcollectionsimportpickleimporttomlimportcopyimportloggingfromdataclassesimportdataclassimportnumpyfromopenquake.baselibimporthdf5fromopenquake.baselib.generalimportgroupby,block_splitterfromopenquake.baselib.nodeimportcontext,striptag,Node,node_to_dictfromopenquake.hazardlibimportgeo,mfd,pmf,source,tom,valid,InvalidFilefromopenquake.hazardlib.tomimportPoissonTOMfromopenquake.hazardlib.calc.filtersimportsplit_sourcefromopenquake.hazardlib.sourceimportNonParametricSeismicSourcefromopenquake.hazardlib.source.multi_faultimportMultiFaultSourceU32=numpy.uint32F32=numpy.float32F64=numpy.float64TWO16=2**16EPSILON=1E-12source_dt=numpy.dtype([('source_id',U32),('num_ruptures',U32),('pik',hdf5.vuint8)])KNOWN_MFDS=('incrementalMFD','truncGutenbergRichterMFD','arbitraryMFD','YoungsCoppersmithMFD','multiMFD','taperedGutenbergRichterMFD')EXCLUDE_FROM_GEOM_PROPS=('Polygon','Point','MultiPoint','LineString','3D MultiLineString','3D MultiPolygon','posList')
[docs]defextract_dupl(values):""" :param values: a sequence of values :returns: the duplicated values """c=collections.Counter(values)return[valueforvalue,countsinc.items()ifcounts>1]
[docs]deffix_dupl(dist,fname=None,lineno=None):""" Fix the distribution if it contains identical values or raise an error. :param dist: a list of pairs [(prob, value)...] for a hypocenter or nodal plane dist :param fname: the file which is being read; if it is None, it means you are writing the distribution: in that case raise an error for duplicated values :param lineno: the line number of the file which is being read (None in writing mode) """n=len(dist)values=collections.defaultdict(float)# dict value -> probability# value can be a scalar (hypocenter depth) or a triple# (strike, dip, rake) for a nodal plane distributiongot=[]forprob,valueindist:ifprob==0:raiseValueError('Zero probability in subnode %s'%value)values[value]+=probgot.append(value)iflen(values)<n:iffnameisNone:# when called from the sourcewriterraiseValueError('There are repeated values in %s'%got)else:logging.info('There were repeated values %s in %s:%s',extract_dupl(got),fname,lineno)assertabs(sum(values.values())-1)<EPSILON# sanity checknewdist=sorted([(p,v)forv,pinvalues.items()])ifisinstance(newdist[0][1],tuple):# nodal planesnewdist=[(p,geo.nodalplane.NodalPlane(*v))forp,vinnewdist]# run hazardlib/tests/data/context/job.ini to check this;# you will get [(0.2, 6.0), (0.2, 8.0), (0.2, 10.0), (0.4, 2.0)]dist[:]=newdist
[docs]defrounded_unique(mags,idxs):""" :param mags: a list of magnitudes :param idxs: a list of tuples of section indices :returns: an array of magnitudes rounded to 2 digits :raises: ValueError if the rounded magnitudes contain duplicates """mags=numpy.round(mags,2)mag_idxs=[(mag,' '.join(idx))formag,idxinzip(mags,idxs)]dupl=extract_dupl(mag_idxs)ifdupl:logging.error('the pair (mag=%s, idxs=%s) is duplicated'%dupl[0])returnmags
[docs]classSourceGroup(collections.abc.Sequence):""" A container for the following parameters: :param str trt: the tectonic region type all the sources belong to :param list sources: a list of hazardlib source objects :param name: The name of the group :param src_interdep: A string specifying if the sources in this cluster are independent or mutually exclusive :param rup_indep: A string specifying if the ruptures within each source of the cluster are independent or mutually exclusive :param weights: A dictionary whose keys are the source IDs of the cluster and the values are the weights associated with each source :param min_mag: the minimum magnitude among the given sources :param max_mag: the maximum magnitude among the given sources :param id: an optional numeric ID (default 0) set by the engine and used when serializing SourceModels to HDF5 :param temporal_occurrence_model: A temporal occurrence model controlling the source group occurrence :param cluster: A boolean indicating if the sources behaves as a cluster similarly to what used by the USGS for the New Madrid in the 2008 National Hazard Model. """changes=0# set in apply_uncertainty
[docs]@classmethoddefcollect(cls,sources):""" :param sources: dictionaries with a key 'tectonicRegion' :returns: an ordered list of SourceGroup instances """source_stats_dict={}forsrcinsources:trt=src['tectonicRegion']iftrtnotinsource_stats_dict:source_stats_dict[trt]=SourceGroup(trt)sg=source_stats_dict[trt]ifnotsg.sources:# we append just one source per SourceGroup, so that# the memory occupation is insignificantsg.sources.append(src)# return SourceGroups, ordered by TRT stringreturnsorted(source_stats_dict.values())
def__init__(self,trt,sources=None,name=None,src_interdep='indep',rup_interdep='indep',grp_probability=1.,min_mag={'default':0},max_mag=None,temporal_occurrence_model=None,cluster=False):# checksself.trt=trtself.sources=[]self.name=nameself.src_interdep=src_interdepself.rup_interdep=rup_interdepself._check_init_variables(sources,name,src_interdep,rup_interdep)self.grp_probability=grp_probabilityself.min_mag=min_magself.max_mag=max_magifsources:forsrcinsorted(sources,key=operator.attrgetter('source_id')):self.update(src)self.source_model=None# to be set later, in FullLogicTreeself.temporal_occurrence_model=temporal_occurrence_modelself.cluster=cluster# check weights in case of mutually exclusive rupturesifrup_interdep=='mutex':forsrcinself.sources:assertisinstance(src,NonParametricSeismicSource)forrup,_insrc.data:assertrup.weightisnotNone@propertydeftom_name(self):""" :returns: name of the associated temporal occurrence model """ifself.temporal_occurrence_model:returnself.temporal_occurrence_model.__class__.__name__else:return'PoissonTOM'@propertydefatomic(self):""" :returns: True if the group cannot be split """return(self.clusterorself.src_interdep=='mutex'orself.rup_interdep=='mutex')@propertydefweight(self):""" :returns: total weight of the underlying sources """returnsum(src.weightforsrcinself)@propertydefcodes(self):""" The codes of the underlying sources as a byte string """codes=set()forsrcinself.sources:codes.add(src.code)returnb''.join(sorted(codes))def_check_init_variables(self,src_list,name,src_interdep,rup_interdep):ifsrc_interdepnotin('indep','mutex'):raiseValueError('source interdependence incorrect %s '%src_interdep)ifrup_interdepnotin('indep','mutex'):raiseValueError('rupture interdependence incorrect %s '%rup_interdep)# check TRTifsrc_list:# can be Noneforsrcinsrc_list:assertsrc.tectonic_region_type==self.trt,(src.tectonic_region_type,self.trt)# Mutually exclusive ruptures can only belong to non-parametric# sourcesifrup_interdep=='mutex':ifnotisinstance(src,NonParametricSeismicSource):msg="Mutually exclusive ruptures can only be "msg+="modelled using non-parametric sources"raiseValueError(msg)
[docs]defupdate(self,src):""" Update the attributes sources, min_mag, max_mag according to the given source. :param src: an instance of :class: `openquake.hazardlib.source.base.BaseSeismicSource` """assertsrc.tectonic_region_type==self.trt,(src.tectonic_region_type,self.trt)# checking mutex rupturesif(notisinstance(src,NonParametricSeismicSource)andself.rup_interdep=='mutex'):msg="Mutually exclusive ruptures can only be "msg+="modelled using non-parametric sources"raiseValueError(msg)self.sources.append(src)_,max_mag=src.get_min_max_mag()prev_max_mag=self.max_magifprev_max_magisNoneormax_mag>prev_max_mag:self.max_mag=max_mag
[docs]defget_trt_smr(self):""" :returns: the .trt_smr attribute of the underlying sources """returnself.sources[0].trt_smr
[docs]defcount_ruptures(self):""" Set src.num_ruptures on each source in the group """forsrcinself:src.nsites=1src.num_ruptures=src.count_ruptures()print(src.weight)returnself
# used only in event_based, where weight = num_ruptures
[docs]defsplit(self,maxweight):""" Split the group in subgroups with weight <= maxweight, unless it it atomic. """ifself.atomic:return[self]# split multipoint/multifault in advancesources=[]forsrcinself:ifsrc.codeinb'MF':sources.extend(split_source(src))else:sources.append(src)out=[]defweight(src):ifsrc.code==b'F':# consider it much heavierreturnsrc.num_ruptures*25returnsrc.num_rupturesforblockinblock_splitter(sources,maxweight,weight):sg=copy.copy(self)sg.sources=blockout.append(sg)logging.info('Produced %d subgroup(s) of %s',len(out),self)returnout
[docs]defget_tom_toml(self,time_span):""" :returns: the TOM as a json string {'PoissonTOM': {'time_span': 50}} """tom=self.temporal_occurrence_modeliftomisNone:return'[PoissonTOM]\ntime_span=%s'%time_spandic={tom.__class__.__name__:vars(tom)}returntoml.dumps(dic)
[docs]defis_poissonian(self):""" :returns: True if all the sources in the group are poissonian """tom=getattr(self.sources[0],'temporal_occurrence_model',None)returntom.__class__.__name__=='PoissonTOM'
def__repr__(self):return'<%s%s, %d source(s), weight=%d>'%(self.__class__.__name__,self.trt,len(self.sources),self.weight)def__lt__(self,other):""" Make sure there is a precise ordering of SourceGroup objects. Objects with less sources are put first; in case the number of sources is the same, use lexicographic ordering on the trts """num_sources=len(self.sources)other_sources=len(other.sources)ifnum_sources==other_sources:returnself.trt<other.trtreturnnum_sources<other_sourcesdef__getitem__(self,i):returnself.sources[i]def__iter__(self):returniter(self.sources)def__len__(self):returnlen(self.sources)def__toh5__(self):lst=[]fori,srcinenumerate(self.sources):buf=pickle.dumps(src,pickle.HIGHEST_PROTOCOL)lst.append((src.id,src.num_ruptures,numpy.frombuffer(buf,numpy.uint8)))attrs=dict(trt=self.trt,name=self.nameor'',src_interdep=self.src_interdep,rup_interdep=self.rup_interdep,grp_probability=self.grp_probabilityor'1')returnnumpy.array(lst,source_dt),attrsdef__fromh5__(self,array,attrs):vars(self).update(attrs)self.sources=[]forrowinarray:self.sources.append(pickle.loads(memoryview(row['pik'])))
[docs]defsplit_coords_2d(seq):""" :param seq: a flat list with lons and lats :returns: a validated list of pairs (lon, lat) >>> split_coords_2d([1.1, 2.1, 2.2, 2.3]) [(1.1, 2.1), (2.2, 2.3)] """lons,lats=[],[]fori,elinenumerate(seq):ifi%2==0:lons.append(valid.longitude(el))elifi%2==1:lats.append(valid.latitude(el))returnlist(zip(lons,lats))
[docs]defsplit_coords_3d(seq):""" :param seq: a flat list with lons, lats and depths :returns: a validated list of (lon, lat, depths) triplets >>> split_coords_3d([1.1, 2.1, 0.1, 2.3, 2.4, 0.1]) [(1.1, 2.1, 0.1), (2.3, 2.4, 0.1)] """lons,lats,depths=[],[],[]fori,elinenumerate(seq):ifi%3==0:lons.append(valid.longitude(el))elifi%3==1:lats.append(valid.latitude(el))elifi%3==2:depths.append(valid.depth(el))returnlist(zip(lons,lats,depths))
[docs]defconvert_nonParametricSeismicSource(fname,node,rup_spacing=5.0):""" Convert the given node into a non parametric source object. :param fname: full pathname to the XML file associated to the node :param node: a Node object coming from an XML file :param rup_spacing: Rupture spacing [km] :returns: a :class:`openquake.hazardlib.source.NonParametricSeismicSource` instance """trt=node.attrib.get('tectonicRegion')rups_weights=Noneif'rup_weights'innode.attrib:rups_weights=F64(node['rup_weights'].split())nps=source.NonParametricSeismicSource(node['id'],node['name'],trt,[],[])nps.splittable='rup_weights'notinnode.attribiffname:path=os.path.splitext(fname)[0]+'.hdf5'hdf5_fname=pathifos.path.exists(path)elseNoneifhdf5_fnameandnode.textisNone:# gridded source, read the rupture data from the HDF5 filewithhdf5.File(hdf5_fname,'r')ash:dic={k:d[:]fork,dinh[node['id']].items()}nps.fromdict(dic,rups_weights)returnnps# read the rupture data from the XML nodesnum_probs=Nonefori,rupnodeinenumerate(node):po=rupnode['probs_occur']probs=pmf.PMF(valid.pmf(po))ifnum_probsisNone:# first timenum_probs=len(probs.data)eliflen(probs.data)!=num_probs:# probs_occur must have uniform length for all rupturesraiseValueError('prob_occurs=%s has %d elements, expected %s'%(po,len(probs.data),num_probs))rup=RuptureConverter(rup_spacing).convert_node(rupnode)rup.tectonic_region_type=trtrup.weight=Noneifrups_weightsisNoneelserups_weights[i]nps.data.append((rup,probs))returnnps
[docs]classRuptureConverter(object):""" Convert ruptures from nodes into Hazardlib ruptures. """fname=None# should be set externallydef__init__(self,rupture_mesh_spacing,complex_fault_mesh_spacing=None):self.rupture_mesh_spacing=rupture_mesh_spacingself.complex_fault_mesh_spacing=(complex_fault_mesh_spacingorrupture_mesh_spacing)
[docs]defconvert_node(self,node):""" Convert the given rupture node into a hazardlib rupture, depending on the node tag. :param node: a node representing a rupture """returngetattr(self,'convert_'+striptag(node.tag))(node)
[docs]defgeo_line(self,edge):""" Utility function to convert a node of kind edge into a :class:`openquake.hazardlib.geo.Line` instance. :param edge: a node describing an edge """withcontext(self.fname,edge.LineString.posList)asplist:coords=split_coords_2d(~plist)returngeo.Line([geo.Point(*p)forpincoords])
[docs]defgeo_lines(self,edges):""" Utility function to convert a list of edges into a list of :class:`openquake.hazardlib.geo.Line` instances. :param edge: a node describing an edge """lines=[]foredgeinedges:withcontext(self.fname,edge):coords=split_coords_3d(~edge.LineString.posList)lines.append(geo.Line([geo.Point(*p)forpincoords]))returnlines
[docs]defgeo_planar(self,surface):""" Utility to convert a PlanarSurface node with subnodes topLeft, topRight, bottomLeft, bottomRight into a :class:`openquake.hazardlib.geo.PlanarSurface` instance. :param surface: PlanarSurface node """withcontext(self.fname,surface):tl=surface.topLefttop_left=geo.Point(tl['lon'],tl['lat'],tl['depth'])tr=surface.topRighttop_right=geo.Point(tr['lon'],tr['lat'],tr['depth'])bl=surface.bottomLeftbottom_left=geo.Point(bl['lon'],bl['lat'],bl['depth'])br=surface.bottomRightbottom_right=geo.Point(br['lon'],br['lat'],br['depth'])returngeo.PlanarSurface.from_corner_points(top_left,top_right,bottom_right,bottom_left)
[docs]defconvert_surfaces(self,surface_nodes,sec_id=''):""" :param surface_nodes: surface nodes as described below Utility to convert a list of surface nodes into a single hazardlib surface. There are four possibilities: 1. there is a single simpleFaultGeometry node; returns a :class:`openquake.hazardlib.geo.simpleFaultSurface` instance 2. there is a single complexFaultGeometry node; returns a :class:`openquake.hazardlib.geo.complexFaultSurface` instance 3. there is a single griddedSurface node; returns a :class:`openquake.hazardlib.geo.GriddedSurface` instance 4. there is either a single planarSurface or a list of planarSurface nodes; returns a :class:`openquake.hazardlib.geo.PlanarSurface` instance or a :class:`openquake.hazardlib.geo.MultiSurface` instance 5. there is either a single kiteSurface or a list of kiteSurface nodes; returns a :class:`openquake.hazardlib.geo.KiteSurface` instance or a :class:`openquake.hazardlib.geo.MultiSurface` instance """surface_node=surface_nodes[0]ifsurface_node.tag.endswith('simpleFaultGeometry'):surface=geo.SimpleFaultSurface.from_fault_data(self.geo_line(surface_node),~surface_node.upperSeismoDepth,~surface_node.lowerSeismoDepth,~surface_node.dip,self.rupture_mesh_spacing)elifsurface_node.tag.endswith('complexFaultGeometry'):surface=geo.ComplexFaultSurface.from_fault_data(self.geo_lines(surface_node),self.complex_fault_mesh_spacing)elifsurface_node.tag.endswith('griddedSurface'):withcontext(self.fname,surface_node):coords=split_coords_3d(~surface_node.posList)points=[geo.Point(*p)forpincoords]surface=geo.GriddedSurface.from_points_list(points)elifsurface_node.tag.endswith('kiteSurface'):# single or multiple kite surfacesprofs=[self.geo_lines(node)fornodeinsurface_nodes]iflen(profs)==1:# there is a single surface_nodesurface=geo.KiteSurface.from_profiles(profs[0],self.rupture_mesh_spacing,self.rupture_mesh_spacing,sec_id=sec_id)else:# normally found in sections.xmlsurfaces=[]forprofinprofs:surfaces.append(geo.KiteSurface.from_profiles(prof,self.rupture_mesh_spacing,self.rupture_mesh_spacing))surface=geo.MultiSurface(surfaces)else:# a collection of planar surfacesiflen(surface_nodes)==1:returnself.geo_planar(surface_nodes[0])planar_surfaces=list(map(self.geo_planar,surface_nodes))surface=geo.MultiSurface(planar_surfaces)returnsurface
[docs]defconvert_simpleFaultRupture(self,node):""" Convert a simpleFaultRupture node. :param node: the rupture node """mag,rake,hypocenter=self.get_mag_rake_hypo(node)withcontext(self.fname,node):surfaces=[node.simpleFaultGeometry]rupt=source.rupture.BaseRupture(mag=mag,rake=rake,tectonic_region_type=None,hypocenter=hypocenter,surface=self.convert_surfaces(surfaces))returnrupt
[docs]defconvert_complexFaultRupture(self,node):""" Convert a complexFaultRupture node. :param node: the rupture node """mag,rake,hypocenter=self.get_mag_rake_hypo(node)withcontext(self.fname,node):[surface]=node.getnodes('complexFaultGeometry')rupt=source.rupture.BaseRupture(mag=mag,rake=rake,tectonic_region_type=None,hypocenter=hypocenter,surface=self.convert_surfaces([surface]))returnrupt
[docs]defconvert_singlePlaneRupture(self,node):""" Convert a singlePlaneRupture node. :param node: the rupture node """mag,rake,hypocenter=self.get_mag_rake_hypo(node)withcontext(self.fname,node):surfaces=[node.planarSurface]rupt=source.rupture.BaseRupture(mag=mag,rake=rake,tectonic_region_type=None,hypocenter=hypocenter,surface=self.convert_surfaces(surfaces))returnrupt
# used in scenarios or nonparametric sources
[docs]defconvert_multiPlanesRupture(self,node):""" Convert a multiPlanesRupture node. :param node: the rupture node """mag,rake,hypocenter=self.get_mag_rake_hypo(node)withcontext(self.fname,node):ifhasattr(node,'planarSurface'):surfaces=list(node.getnodes('planarSurface'))forsinsurfaces:asserts.tag.endswith('planarSurface')elifhasattr(node,'kiteSurface'):surfaces=list(node.getnodes('kiteSurface'))forsinsurfaces:asserts.tag.endswith('kiteSurface')else:raiseValueError('Only multiSurfaces of planarSurfaces or''kiteSurfaces are supported (no mix)')rupt=source.rupture.BaseRupture(mag=mag,rake=rake,tectonic_region_type=None,hypocenter=hypocenter,surface=self.convert_surfaces(surfaces))returnrupt
[docs]defconvert_griddedRupture(self,node):""" Convert a griddedRupture node. :param node: the rupture node """mag,rake,hypocenter=self.get_mag_rake_hypo(node)withcontext(self.fname,node):surfaces=[node.griddedSurface]rupt=source.rupture.BaseRupture(mag=mag,rake=rake,tectonic_region_type=None,hypocenter=hypocenter,surface=self.convert_surfaces(surfaces))returnrupt
[docs]defconvert_ruptureCollection(self,node):""" :param node: a ruptureCollection node :returns: a dictionary trt_smr -> EBRuptures """coll={}forgrpnodeinnode:trt_smr=int(grpnode['id'])coll[trt_smr]=ebrs=[]fornodeingrpnode:rup=self.convert_node(node)rup.rup_id=int(node['id'])sesnodes=node.stochasticEventSetsn=0# number of eventsforsesnodeinsesnodes:withcontext(self.fname,sesnode):n+=len(sesnode.text.split())ebr=source.rupture.EBRupture(rup,0,0,numpy.array([n]))ebrs.append(ebr)returncoll
[docs]classSourceConverter(RuptureConverter):""" Convert sources from valid nodes into Hazardlib objects. """def__init__(self,investigation_time=50.,rupture_mesh_spacing=5.,complex_fault_mesh_spacing=None,width_of_mfd_bin=1.0,area_source_discretization=None,minimum_magnitude={'default':0},source_id=None,discard_trts=(),floating_x_step=0,floating_y_step=0,source_nodes=(),infer_occur_rates=False):self.investigation_time=investigation_timeself.area_source_discretization=area_source_discretizationself.minimum_magnitude=minimum_magnitudeself.rupture_mesh_spacing=rupture_mesh_spacingself.complex_fault_mesh_spacing=(complex_fault_mesh_spacingorrupture_mesh_spacing)self.width_of_mfd_bin=width_of_mfd_binself.source_id=source_idself.discard_trts=discard_trtsself.floating_x_step=floating_x_stepself.floating_y_step=floating_y_stepself.source_nodes=source_nodesself.infer_occur_rates=infer_occur_rates
[docs]defconvert_node(self,node):""" Convert the given source node into a hazardlib source, depending on the node tag. :param node: a node representing a source or a SourceGroup """trt=node.attrib.get('tectonicRegion')iftrtandtrtinself.discard_trts:returnname=striptag(node.tag)ifname.endswith('Source'):# source nodesource_id=node['id']ifself.source_idandsource_idnotinself.source_id:# if source_id is set in the job.ini, discard all other sourcesreturnelifself.source_nodesandnamenotinself.source_nodes:# if source_nodes is set, discard all other source nodesreturnobj=getattr(self,'convert_'+name)(node)ifhasattr(obj,'mfd')andhasattr(obj.mfd,'slip_rate'):# TruncatedGRMFD with slip rate (for Slovenia)m=obj.mfdobj.mfd=m.from_slip_rate(m.min_mag,m.max_mag,m.bin_width,m.b_val,m.slip_rate,m.rigidity,obj.get_fault_surface_area())returnobj
[docs]defconvert_geometryModel(self,node):""" :param node: a geometryModel node :returns: a dictionary sec_id -> section """sections={secnode["id"]:self.convert_node(secnode)forsecnodeinnode}returnsections
[docs]defconvert_section(self,node):""" :param node: a section node :returns: a list of surfaces """withcontext(self.fname,node):ifhasattr(node,'planarSurface'):surfaces=list(node.getnodes('planarSurface'))elifhasattr(node,'kiteSurface'):surfaces=list(node.getnodes('kiteSurface'))else:raiseValueError('Only planarSurfaces or kiteSurfaces '+'supported')returnself.convert_surfaces(surfaces,node['id'])
[docs]defget_tom(self,node):""" Convert the given node into a Temporal Occurrence Model object. :param node: a node of kind poissonTOM or similar :returns: a :class:`openquake.hazardlib.tom.BaseTOM` instance """occurrence_rate=node.get('occurrence_rate')kwargs={}# the occurrence_rate is not None only for clusters of sources,# the ones implemented in calc.hazard_curve, see test case_35ifoccurrence_rate:tom_cls=tom.registry['ClusterPoissonTOM']returntom_cls(self.investigation_time,occurrence_rate)if'tom'innode.attrib:tom_cls=tom.registry[node['tom']]# if tom is negbinom, sets mu and alpha attr to tom_classifnode['tom']=='NegativeBinomialTOM':kwargs={'alpha':float(node['alpha']),'mu':float(node['mu'])}else:tom_cls=tom.registry['PoissonTOM']returntom_cls(time_span=self.investigation_time,**kwargs)
[docs]defconvert_mfdist(self,node):""" Convert the given node into a Magnitude-Frequency Distribution object. :param node: a node of kind incrementalMFD or truncGutenbergRichterMFD :returns: a :class:`openquake.hazardlib.mfd.EvenlyDiscretizedMFD.` or :class:`openquake.hazardlib.mfd.TruncatedGRMFD` instance """withcontext(self.fname,node):[mfd_node]=[subnodeforsubnodeinnodeifsubnode.tag.endswith(KNOWN_MFDS)]withcontext(self.fname,mfd_node):ifmfd_node.tag.endswith('incrementalMFD'):returnmfd.EvenlyDiscretizedMFD(min_mag=mfd_node['minMag'],bin_width=mfd_node['binWidth'],occurrence_rates=~mfd_node.occurRates)elifmfd_node.tag.endswith('truncGutenbergRichterMFD'):slip_rate=mfd_node.get('slipRate')rigidity=mfd_node.get('rigidity')ifslip_rate:assertrigidity# instantiate with an area of 1, to be fixed later ongr_mfd=mfd.TruncatedGRMFD.from_slip_rate(mfd_node['minMag'],mfd_node['maxMag'],self.width_of_mfd_bin,mfd_node['bValue'],slip_rate,rigidity,area=1)else:gr_mfd=mfd.TruncatedGRMFD(a_val=mfd_node['aValue'],b_val=mfd_node['bValue'],min_mag=mfd_node['minMag'],max_mag=mfd_node['maxMag'],bin_width=self.width_of_mfd_bin)returngr_mfdelifmfd_node.tag.endswith('arbitraryMFD'):returnmfd.ArbitraryMFD(magnitudes=~mfd_node.magnitudes,occurrence_rates=~mfd_node.occurRates)elifmfd_node.tag.endswith('YoungsCoppersmithMFD'):returnmfd.YoungsCoppersmith1985MFD(min_mag=mfd_node["minMag"],b_val=mfd_node["bValue"],char_mag=mfd_node["characteristicMag"],char_rate=mfd_node.get("characteristicRate"),total_moment_rate=mfd_node.get("totalMomentRate"),bin_width=mfd_node["binWidth"])elifmfd_node.tag.endswith('multiMFD'):returnmfd.multi_mfd.MultiMFD.from_node(mfd_node,self.width_of_mfd_bin)elifmfd_node.tag.endswith('taperedGutenbergRichterMFD'):returnmfd.TaperedGRMFD(mfd_node['minMag'],mfd_node['maxMag'],mfd_node['cornerMag'],self.width_of_mfd_bin,mfd_node['aValue'],mfd_node['bValue'])
[docs]defconvert_npdist(self,node):""" Convert the given node into a Nodal Plane Distribution. :param node: a nodalPlaneDist node :returns: a :class:`openquake.hazardlib.geo.NodalPlane` instance """withcontext(self.fname,node):npnode=node.nodalPlaneDistnpdist=[]fornpinnpnode:prob,strike,dip,rake=(np['probability'],np['strike'],np['dip'],np['rake'])npdist.append((prob,geo.NodalPlane(strike,dip,rake)))withcontext(self.fname,npnode):fix_dupl(npdist,self.fname,npnode.lineno)returnpmf.PMF(npdist)
[docs]defconvert_hddist(self,node):""" Convert the given node into a probability mass function for the hypo depth distribution. :param node: a hypoDepthDist node :returns: a :class:`openquake.hazardlib.pmf.PMF` instance """withcontext(self.fname,node):hdnode=node.hypoDepthDisthddist=[(hd['probability'],hd['depth'])forhdinhdnode]withcontext(self.fname,hdnode):fix_dupl(hddist,self.fname,hdnode.lineno)returnpmf.PMF(hddist)
[docs]defconvert_areaSource(self,node):""" Convert the given node into an area source object. :param node: a node with tag areaGeometry :returns: a :class:`openquake.hazardlib.source.AreaSource` instance """geom=node.areaGeometrycoords=split_coords_2d(~geom.Polygon.exterior.LinearRing.posList)polygon=geo.Polygon([geo.Point(*xy)forxyincoords])msr=~node.magScaleRelarea_discretization=geom.attrib.get('discretization',self.area_source_discretization)ifarea_discretizationisNone:raiseValueError('The source %r has no `discretization` parameter and the job.''ini file has no `area_source_discretization` parameter either'%node['id'])returnsource.AreaSource(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=self.convert_mfdist(node),rupture_mesh_spacing=self.rupture_mesh_spacing,magnitude_scaling_relationship=msr,rupture_aspect_ratio=~node.ruptAspectRatio,upper_seismogenic_depth=~geom.upperSeismoDepth,lower_seismogenic_depth=~geom.lowerSeismoDepth,nodal_plane_distribution=self.convert_npdist(node),hypocenter_distribution=self.convert_hddist(node),polygon=polygon,area_discretization=area_discretization,temporal_occurrence_model=self.get_tom(node))
[docs]defconvert_pointSource(self,node):""" Convert the given node into a point source object. :param node: a node with tag pointGeometry :returns: a :class:`openquake.hazardlib.source.PointSource` instance """geom=node.pointGeometrylon_lat=~geom.Point.posmsr=~node.magScaleRelreturnsource.PointSource(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=self.convert_mfdist(node),rupture_mesh_spacing=self.rupture_mesh_spacing,magnitude_scaling_relationship=msr,rupture_aspect_ratio=~node.ruptAspectRatio,upper_seismogenic_depth=~geom.upperSeismoDepth,lower_seismogenic_depth=~geom.lowerSeismoDepth,location=geo.Point(*lon_lat),nodal_plane_distribution=self.convert_npdist(node),hypocenter_distribution=self.convert_hddist(node),temporal_occurrence_model=self.get_tom(node))
[docs]defconvert_multiPointSource(self,node):""" Convert the given node into a MultiPointSource object. :param node: a node with tag multiPointGeometry :returns: a :class:`openquake.hazardlib.source.MultiPointSource` """geom=node.multiPointGeometrylons,lats=zip(*split_coords_2d(~geom.posList))msr=~node.magScaleRelreturnsource.MultiPointSource(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=self.convert_mfdist(node),magnitude_scaling_relationship=msr,rupture_aspect_ratio=~node.ruptAspectRatio,upper_seismogenic_depth=~geom.upperSeismoDepth,lower_seismogenic_depth=~geom.lowerSeismoDepth,nodal_plane_distribution=self.convert_npdist(node),hypocenter_distribution=self.convert_hddist(node),mesh=geo.Mesh(F32(lons),F32(lats)),temporal_occurrence_model=self.get_tom(node))
[docs]defconvert_simpleFaultSource(self,node):""" Convert the given node into a simple fault object. :param node: a node with tag areaGeometry :returns: a :class:`openquake.hazardlib.source.SimpleFaultSource` instance """geom=node.simpleFaultGeometrymsr=~node.magScaleRelfault_trace=self.geo_line(geom)mfd=self.convert_mfdist(node)withcontext(self.fname,node):try:hypo_list=valid.hypo_list(node.hypoList)exceptAttributeError:hypo_list=()try:slip_list=valid.slip_list(node.slipList)exceptAttributeError:slip_list=()simple=source.SimpleFaultSource(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=mfd,rupture_mesh_spacing=self.rupture_mesh_spacing,magnitude_scaling_relationship=msr,rupture_aspect_ratio=~node.ruptAspectRatio,upper_seismogenic_depth=~geom.upperSeismoDepth,lower_seismogenic_depth=~geom.lowerSeismoDepth,fault_trace=fault_trace,dip=~geom.dip,rake=~node.rake,temporal_occurrence_model=self.get_tom(node),hypo_list=hypo_list,slip_list=slip_list)returnsimple
[docs]defconvert_kiteFaultSource(self,node):""" Convert the given node into a kite fault object. :param node: a node with tag kiteFaultSource :returns: a :class:`openquake.hazardlib.source.KiteFaultSource` instance """as_kite=Truetry:geom=node.simpleFaultGeometryfault_trace=self.geo_line(geom)as_kite=FalseexceptException:geom=node.kiteSurfaceprofiles=self.geo_lines(geom)msr=~node.magScaleRelmfd=self.convert_mfdist(node)# get rupture floating stepsxstep=self.floating_x_stepystep=self.floating_y_stepwithcontext(self.fname,node):ifas_kite:outsrc=source.KiteFaultSource(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=mfd,rupture_mesh_spacing=self.rupture_mesh_spacing,magnitude_scaling_relationship=msr,rupture_aspect_ratio=~node.ruptAspectRatio,temporal_occurrence_model=self.get_tom(node),profiles=profiles,floating_x_step=xstep,floating_y_step=ystep,rake=~node.rake,profiles_sampling=None)else:outsrc=source.KiteFaultSource.as_simple_fault(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=mfd,rupture_mesh_spacing=self.rupture_mesh_spacing,magnitude_scaling_relationship=msr,rupture_aspect_ratio=~node.ruptAspectRatio,upper_seismogenic_depth=~geom.upperSeismoDepth,lower_seismogenic_depth=~geom.lowerSeismoDepth,fault_trace=fault_trace,dip=~geom.dip,rake=~node.rake,temporal_occurrence_model=self.get_tom(node),floating_x_step=xstep,floating_y_step=ystep)returnoutsrc
[docs]defconvert_complexFaultSource(self,node):""" Convert the given node into a complex fault object. :param node: a node with tag areaGeometry :returns: a :class:`openquake.hazardlib.source.ComplexFaultSource` instance """geom=node.complexFaultGeometryedges=self.geo_lines(geom)mfd=self.convert_mfdist(node)msr=~node.magScaleRelwithcontext(self.fname,node):cmplx=source.ComplexFaultSource(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=mfd,rupture_mesh_spacing=self.complex_fault_mesh_spacing,magnitude_scaling_relationship=msr,rupture_aspect_ratio=~node.ruptAspectRatio,edges=edges,rake=~node.rake,temporal_occurrence_model=self.get_tom(node))returncmplx
[docs]defconvert_characteristicFaultSource(self,node):""" Convert the given node into a characteristic fault object. :param node: a characteristicFaultSource node :returns: a :class:`openquake.hazardlib.source.CharacteristicFaultSource` instance """char=source.CharacteristicFaultSource(source_id=node['id'],name=node['name'],tectonic_region_type=node.attrib.get('tectonicRegion'),mfd=self.convert_mfdist(node),surface=self.convert_surfaces(node.surface),rake=~node.rake,temporal_occurrence_model=self.get_tom(node))returnchar
[docs]defconvert_nonParametricSeismicSource(self,node):""" Convert the given node into a non parametric source object. :param node: a node with tag areaGeometry :returns: a :class:`openquake.hazardlib.source.NonParametricSeismicSource` instance """returnconvert_nonParametricSeismicSource(self.fname,node,self.rupture_mesh_spacing)
# used in UCERF
[docs]defconvert_multiFaultSource(self,node):""" Convert the given node into a multi fault source object. :param node: a node with tag multiFaultSource :returns: a :class:`openquake.hazardlib.source.multiFaultSource` instance """sid=node.get('id')name=node.get('name')trt=node.get('tectonicRegion')path=os.path.splitext(self.fname)[0]+'.hdf5'hdf5_fname=pathifos.path.exists(path)elseNoneifhdf5_fnameandnode.textisNone:# read the rupture data from the HDF5 filewithhdf5.File(hdf5_fname,'r')ash:dic={k:d[:]fork,dinh[node['id']].items()}withcontext(self.fname,node):idxs=[x.decode('utf8').split()forxindic['rupture_idxs']]mags=rounded_unique(dic['mag'],idxs)# NB: the sections will be fixed later on, in source_readermfs=MultiFaultSource(sid,name,trt,idxs,dic['probs_occur'],mags,dic['rake'],self.investigation_time,self.infer_occur_rates)returnmfsprobs=[]mags=[]rakes=[]idxs=[]num_probs=Nonefori,rupnodeinenumerate(node):withcontext(self.fname,rupnode):prb=valid.probabilities(rupnode['probs_occur'])ifnum_probsisNone:# first timenum_probs=len(prb)eliflen(prb)!=num_probs:# probs_occur must have uniform length for all ruptureswithcontext(self.fname,rupnode):raiseValueError('prob_occurs=%s has %d elements, expected %s'%(rupnode['probs_occur'],len(prb),num_probs))probs.append(prb)mags.append(~rupnode.magnitude)rakes.append(~rupnode.rake)indexes=rupnode.sectionIndexes['indexes']idxs.append(tuple(indexes.split(',')))withcontext(self.fname,node):mags=rounded_unique(mags,idxs)rakes=numpy.array(rakes)# NB: the sections will be fixed later on, in source_readermfs=MultiFaultSource(sid,name,trt,idxs,probs,mags,rakes,self.investigation_time,self.infer_occur_rates)returnmfs
[docs]defconvert_sourceGroup(self,node):""" Convert the given node into a SourceGroup object. :param node: a node with tag sourceGroup :returns: a :class:`SourceGroup` instance """trt=node['tectonicRegion']srcs_weights=node.attrib.get('srcs_weights')grp_attrs={k:vfork,vinnode.attrib.items()ifknotin('name','src_interdep','rup_interdep','srcs_weights')}ifnode.attrib.get('src_interdep')!='mutex':# ignore weights set to 1 in old versions of the enginesrcs_weights=Nonesg=SourceGroup(trt,min_mag=self.minimum_magnitude)sg.temporal_occurrence_model=self.get_tom(node)sg.name=node.attrib.get('name')# Set attributes related to occurrencesg.src_interdep=node.attrib.get('src_interdep','indep')sg.rup_interdep=node.attrib.get('rup_interdep','indep')sg.grp_probability=node.attrib.get('grp_probability',1)# Set the cluster attributesg.cluster=node.attrib.get('cluster')=='true'# Filter admitted cases# 1. The source group is a cluster. In this case the cluster must have# the attributes required to define its occurrence in time.ifsg.cluster:msg='A cluster group requires the definition of a temporal'msg+=' occurrence model'assert'tom'innode.attrib,msgifisinstance(tom,PoissonTOM):# hack in place of a ClusterPoissonTOMasserthasattr(sg,'occurrence_rate')forsrc_nodeinnode:src=self.convert_node(src_node)ifsrcisNone:# filtered out by source_idcontinue# transmit the group attributes to the underlying sourceforattr,valueingrp_attrs.items():ifattr=='tectonicRegion':src_trt=src_node.get('tectonicRegion')ifsrc_trtandsrc_trt!=trt:withcontext(self.fname,src_node):raiseValueError('Found %s, expected %s'%(src_node['tectonicRegion'],trt))src.tectonic_region_type=trtelifattr=='grp_probability':pass# do not transmitelse:# transmit as it issetattr(src,attr,node[attr])sg.update(src)ifsgandsg.src_interdep=='mutex':# sg can be empty if source_id is specified and it is different# from any source in sgiflen(node)andlen(srcs_weights)!=len(node):raiseValueError('There are %d srcs_weights but %d source(s) in %s'%(len(srcs_weights),len(node),self.fname))tot=0withcontext(self.fname,node):forsrc,swinzip(sg,srcs_weights):if':'notinsrc.source_id:raiseNameError('You must use the colon convention ''with mutex sources')src.mutex_weight=swtot+=swnumpy.testing.assert_allclose(tot,1.,err_msg='sum(srcs_weights)',atol=5E-6)# check that, when the cluster option is set, the group has a temporal# occurrence model properly definedifsg.clusterandnothasattr(sg,'temporal_occurrence_model'):msg='The Source Group is a cluster but does not have a 'msg+='temporal occurrence model'raiseValueError(msg)returnsg
def_planar(surface):poly=[]tl=surface.topLeftpoly.append((tl['lon'],tl['lat'],tl['depth']))tr=surface.topRightpoly.append((tr['lon'],tr['lat'],tr['depth']))br=surface.bottomRightpoly.append((br['lon'],br['lat'],br['depth']))bl=surface.bottomLeftpoly.append((bl['lon'],bl['lat'],bl['depth']))poly.append((tl['lon'],tl['lat'],tl['depth']))# close the polygonreturn[poly]
[docs]classRowConverter(SourceConverter):""" Used in the command oq nrml_to_csv to convert source models into Row objects. """
[docs]defconvert_node(self,node):""" Convert the given source node into a Row object """trt=node.attrib.get('tectonicRegion')iftrtandtrtinself.discard_trts:returnwithcontext(self.fname,node):returngetattr(self,'convert_'+striptag(node.tag))(node)
[docs]defconvert_geomprops(self,node):# NOTE: node_to_dict(node) returns a dict having the geometry type as# key and the corresponding properties as value, so we get the first# value to retrieve the information we needfull_geom_props=list(node_to_dict(node).values())[0]geom_props={k:full_geom_props[k]forkinfull_geom_propsifknotinEXCLUDE_FROM_GEOM_PROPS}returnstr(geom_props)
[docs]defconvert_areaSource(self,node):geom=node.areaGeometrycoords=split_coords_2d(~geom.Polygon.exterior.LinearRing.posList)ifcoords[0]!=coords[-1]:coords+=[coords[0]]# close the polygonreturnRow(node['id'],node['name'],'A',node.get('groupname',''),node.get('tectonicRegion',''),self.convert_mfdist(node),str(~node.magScaleRel),~node.ruptAspectRatio,~geom.upperSeismoDepth,~geom.lowerSeismoDepth,self.convert_npdist(node),self.convert_hddist(node),self.convert_hypolist(node),self.convert_sliplist(node),self.convert_rake(node),self.convert_geomprops(geom),'Polygon',[coords])
[docs]defmultikey(node):""" :returns: (usd, lsd, rar, hddist, npdist, magScaleRel) for the given node """hd=tuple((node['probability'],node['depth'])fornodeinnode.hypoDepthDist)npd=tuple(((node['probability'],node['rake'],node['strike'],node['dip']))fornodeinnode.nodalPlaneDist)geom=node.pointGeometryreturn(round(~geom.upperSeismoDepth,1),round(~geom.lowerSeismoDepth,1),~node.ruptAspectRatio,hd,npd,str(~node.magScaleRel))
[docs]defcollapse(array):""" Collapse a homogeneous array into a scalar; do nothing if the array is not homogenous """iflen(set(aforainarray))==1:# homogenous arrayreturnarray[0]returnarray
[docs]defmfds2multimfd(mfds):""" Convert a list of MFD nodes into a single MultiMFD node """_,kind=mfds[0].tag.split('}')node=Node('multiMFD',dict(kind=kind,size=len(mfds)))lengths=Noneforfieldinmfd.multi_mfd.ASSOC[kind][1:]:alias=mfd.multi_mfd.ALIAS.get(field,field)iffieldin('magnitudes','occurRates'):data=[~getattr(m,field)forminmfds]lengths=[len(d)fordindata]data=sum(data,[])# list of listselse:try:data=[m[alias]forminmfds]exceptKeyError:ifalias=='binWidth':# missing bindWidth in GR MDFs is okcontinueelse:raisenode.append(Node(field,text=collapse(data)))iflengths:# this is the last field if presentnode.append(Node('lengths',text=collapse(lengths)))returnnode
def_pointsources2multipoints(srcs,i):# converts pointSources with the same hddist, npdist and msr into a# single multiPointSource.allsources=[]for(usd,lsd,rar,hd,npd,msr),sourcesingroupby(srcs,multikey).items():iflen(sources)==1:# there is a single sourceallsources.extend(sources)continuemfds=[src[3]forsrcinsources]points=[]forsrcinsources:pg=src.pointGeometrypoints.extend(~pg.Point.pos)geom=Node('multiPointGeometry')geom.append(Node('gml:posList',text=points))geom.append(Node('upperSeismoDepth',text=usd))geom.append(Node('lowerSeismoDepth',text=lsd))node=Node('multiPointSource',dict(id='mps-%d'%i,name='multiPointSource-%d'%i),nodes=[geom])node.append(Node("magScaleRel",text=collapse(msr)))node.append(Node("ruptAspectRatio",text=rar))node.append(mfds2multimfd(mfds))node.append(Node('nodalPlaneDist',nodes=[Node('nodalPlane',dict(probability=prob,rake=rake,strike=strike,dip=dip))forprob,rake,strike,dipinnpd]))node.append(Node('hypoDepthDist',nodes=[Node('hypoDepth',dict(depth=depth,probability=prob))forprob,depthinhd]))allsources.append(node)i+=1returni,allsources
[docs]defdrop_trivial_weights(group):ws=group.attrib.get('srcs_weights')ifwsandlen(set(ws))==1:# all equaldelgroup.attrib['srcs_weights']
[docs]defupdate_source_model(sm_node,fname):""" :param sm_node: a sourceModel Node object containing sourceGroups """i=0forgroupinsm_node:ifnotgroup.tag.endswith('sourceGroup'):raiseInvalidFile('wrong NRML, got %s instead of ''sourceGroup in %s'%(group.tag,fname))drop_trivial_weights(group)psrcs=[]others=[]forsrcingroup:try:delsrc.attrib['tectonicRegion']# make the trt implicitexceptKeyError:pass# already missingifsrc.tag.endswith('pointSource'):psrcs.append(src)else:others.append(src)others.sort(key=lambdasrc:(src.tag,src['id']))i,sources=_pointsources2multipoints(psrcs,i)group.nodes=sources+others