diff --git a/debian/changelog b/debian/changelog index 8b1f76465e39..807a7710f603 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,11 @@ [Anirudh Rao] * Speed up `truncation_level = 0` case for the conditioned gmfs calculator * Treat ShakeMap grid.xml values as medians and not as arithmetic means + * Refactor spatial and cross-IMT correlation models into a common registry, + with one module per model and backward-compatible legacy imports + * Standardize correlation settings across regular and ShakeMap calculations + * Correct the Heresi and Miranda model publication year and document the + references for the implemented correlation models [Michele Simionato] * Vectorized MultiEventRNG.beta and doubled the speed of the Global Risk Model diff --git a/doc/getting-started/demos-tutorials/index.rst b/doc/getting-started/demos-tutorials/index.rst index 06bbb53589d7..766be6b5e7af 100644 --- a/doc/getting-started/demos-tutorials/index.rst +++ b/doc/getting-started/demos-tutorials/index.rst @@ -521,8 +521,8 @@ A demo showing an example of Event Based PSHA calculation is provided with the f [event_based_params] ses_per_logic_tree_path = 100 - ground_motion_correlation_model = - ground_motion_correlation_params = + spatial_correlation_model = + spatial_correlation_params = [output] export_dir = ... diff --git a/doc/user-guide/advanced/advanced-calculations.rst b/doc/user-guide/advanced/advanced-calculations.rst index 83a55081da6b..40ac8c992879 100644 --- a/doc/user-guide/advanced/advanced-calculations.rst +++ b/doc/user-guide/advanced/advanced-calculations.rst @@ -322,7 +322,7 @@ In order to perform a conditional spectrum calculation you need to specify, in a classical calculation: 1. a reference intensity measure type (i.e. imt_ref = SA(0.2)) -2. a cross correlation model (i.e. cross_correlation = BakerJayaram2008) +2. a cross correlation model (i.e. cross_imt_correlation_model = BakerJayaram2008) 3. a set of poes (i.e. poes = 0.01 0.1) The engine will compute a mean conditional spectrum for each ``poe`` and site, as well as the usual mean uniform hazard diff --git a/doc/user-guide/configuration-file/event-based-psha-config.rst b/doc/user-guide/configuration-file/event-based-psha-config.rst index 63d016453241..18a31596042c 100644 --- a/doc/user-guide/configuration-file/event-based-psha-config.rst +++ b/doc/user-guide/configuration-file/event-based-psha-config.rst @@ -28,8 +28,8 @@ in the ``calculation_configuration`` part). Additionally, in this section the us model to be used for the generation of ground motion fields.:: ses_per_logic_tree_path = 5 - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} The acceptable flags for the parameter ``vs30_clustering`` are ``False`` and ``True``, with a capital ``F`` and ``T`` respectively. ``0`` and ``1`` are also acceptable flags. diff --git a/doc/user-guide/configuration-file/event-based-risk-config.rst b/doc/user-guide/configuration-file/event-based-risk-config.rst index 161a04d2d4ac..cad7f44d06ef 100644 --- a/doc/user-guide/configuration-file/event-based-risk-config.rst +++ b/doc/user-guide/configuration-file/event-based-risk-config.rst @@ -42,8 +42,8 @@ event based damage calculation is shown in the listing below.:: number_of_logic_tree_samples = 0 [correlation] - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} [hazard_calculation] random_seed = 24 @@ -129,8 +129,8 @@ event based risk calculation is shown in the listing below.:: gsim_logic_tree_file = gsim_logic_tree.xml [correlation] - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} [hazard_calculation] random_seed = 24 diff --git a/doc/user-guide/configuration-file/scenario-hazard-config.rst b/doc/user-guide/configuration-file/scenario-hazard-config.rst index 83e7af1b29b2..2a3ec572df3f 100644 --- a/doc/user-guide/configuration-file/scenario-hazard-config.rst +++ b/doc/user-guide/configuration-file/scenario-hazard-config.rst @@ -28,8 +28,8 @@ The basic job configuration file required for running a scenario hazard calculat site_model_file = site_model.csv site_model_stations.csv [correlation] - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} [hazard_calculation] intensity_measure_types = PGA, SA(0.3), SA(1.0) diff --git a/doc/user-guide/configuration-file/scenario-risk-config.rst b/doc/user-guide/configuration-file/scenario-risk-config.rst index e9f66c3728cd..c43090024cfd 100644 --- a/doc/user-guide/configuration-file/scenario-risk-config.rst +++ b/doc/user-guide/configuration-file/scenario-risk-config.rst @@ -101,8 +101,8 @@ damage distribution for a portfolio of assets due to the ground motion fields. gsim = BooreAtkinson2008 intensity_measure_types = PGA, SA(0.3) number_of_ground_motion_fields = 1000 - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} **Scenario damage example**:: @@ -306,8 +306,8 @@ The only change that needs to be made in the hazard job configuration file is to gsim_logic_tree_file = gsim_logic_tree.xml intensity_measure_types = PGA, SA(0.3) number_of_ground_motion_fields = 1000 - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} **Example 5** @@ -338,8 +338,8 @@ is shown in the listing below.:: maximum_distance = 200.0 gsim = BooreAtkinson2008 number_of_ground_motion_fields = 1000 - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} [fragility] structural_fragility_file = structural_fragility_model.xml @@ -411,8 +411,8 @@ is shown in the listing below.:: maximum_distance = 200.0 gsim = BooreAtkinson2008 number_of_ground_motion_fields = 1000 - ground_motion_correlation_model = JB2009 - ground_motion_correlation_params = {"vs30_clustering": True} + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} [vulnerability] structural_vulnerability_file = structural_vulnerability_model.xml diff --git a/doc/user-guide/workflows/scenario-hazard.rst b/doc/user-guide/workflows/scenario-hazard.rst index b2eb68466cf2..ba677e09b317 100644 --- a/doc/user-guide/workflows/scenario-hazard.rst +++ b/doc/user-guide/workflows/scenario-hazard.rst @@ -316,8 +316,9 @@ file like the following:: number_of_ground_motion_fields = 10 truncation_level = 3 shakemap_id = usp000fjta - spatial_correlation = yes - cross_correlation = yes + spatial_correlation_model = JayaramBaker2009 + spatial_correlation_params = {"vs30_clustering": True} + cross_imt_correlation_model = BakerCornell2006 This example refers to the 2007 Mw8.0 Pisco earthquake in Peru (see https://earthquake.usgs.gov/earthquakes/eventpage/usp000fjta#shakemap). @@ -385,27 +386,16 @@ Irrespective of the input, the engine will perform the following operations: Correlation *********** -By default the engine tries to compute both the spatial correlation and the cross -correlation between different intensity measure types. Please note that if you -are using MMI as intensity measure type in your vulnerability model, it is not -possible to apply correlations since those are based on physical measures. +ShakeMap and regular calculations use the same named correlation models. Set +``spatial_correlation_model`` and/or ``cross_imt_correlation_model`` to a +registered model name. Omitting a parameter disables that kind of correlation. +The example above preserves the historical ShakeMap defaults: Jayaram and Baker +(2009) spatial correlation with clustered Vs30, and Baker and Cornell (2006) +cross-IMT correlation. -For each kind of correlation you have three choices, that you can set in the -*job.ini*, for a total of nine combinations:: - - - spatial_correlation = yes, cross_correlation = yes # the default - - spatial_correlation = no, cross_correlation = no # disable everything - - spatial_correlation = yes, cross_correlation = no - - spatial_correlation = no, cross_correlation = yes - - spatial_correlation = full, cross_correlation = full - - spatial_correlation = yes, cross_correlation = full - - spatial_correlation = no, cross_correlation = full - - spatial_correlation = full, cross_correlation = no - - spatial_correlation = full, cross_correlation = yes - -yes means using the correlation matrix of the `Silva-Horspool `__ -paper; *no* mean using no correlation; *full* means using an all-ones correlation -matrix. +MMI is not a physical ground-motion measure, so correlation models configured +for MMI-only ShakeMaps are ignored. Other IMTs are checked against each +model's declared scope before the calculation starts. Apart from performance considerations, disabling either the spatial correlation or the cross correlation (or both) might be useful to see how significant the @@ -438,8 +428,9 @@ is feasible, so the risk computation can be performed. Clearly in situations in which the number of hazard sites is too large, approximations will have to be made such as using a larger *region_grid_spacing*. -Disabling spatial AND cross correlation makes it possible run much larger -calculations. The performance can be further increased by not using a ``truncation_level``. +Omitting both correlation model parameters makes it possible to run much larger +calculations. The performance can be further increased by not using a +``truncation_level``. When applying correlation, a soft cap on the size of the calculations is defined. This is done and modifiable through the parameter ``cholesky_limit`` which refers to @@ -463,4 +454,4 @@ Mean ground motion field by GMPE, used to reduce disk space and make risk calcul Mean ground motion field by event, used for debugging/plotting purposes. -Single-rupture hazardlib mean ground motion field, used for analysis/plotting purposes. \ No newline at end of file +Single-rupture hazardlib mean ground motion field, used for analysis/plotting purposes. diff --git a/openquake/calculators/base.py b/openquake/calculators/base.py index 0c5b8ccbc596..8540a3c8ff7f 100644 --- a/openquake/calculators/base.py +++ b/openquake/calculators/base.py @@ -617,7 +617,7 @@ def read_inputs(self): self._read_risk4() if (oq.calculation_mode == 'event_based' and - oq.ground_motion_correlation_model and + oq.spatial_correlation_model and len(self.sitecol) > oq.max_sites_correl): raise ValueError('You cannot use a correlation model with ' f'{self.N} sites [{oq.max_sites_correl=}]') @@ -1784,7 +1784,8 @@ def store_gmfs_from_shakemap(calc, haz_sitecol, assetcol): # calculations with MMI should be executed if len(oq.imtls) == 1: # only MMI intensities - if oq.spatial_correlation != 'no' or oq.cross_correlation != 'no': + if (oq.spatial_correlation_model or + oq.cross_imt_correlation_model): logging.warning('Calculations with MMI intensities do not ' 'support correlation. No correlations ' 'are applied.') @@ -1800,11 +1801,14 @@ def store_gmfs_from_shakemap(calc, haz_sitecol, assetcol): # no MMI intensities, calculation with or without correlation if oq.impact: gmf_dict = {'kind': 'basic'} # possibly add correlation - elif oq.spatial_correlation != 'no' or oq.cross_correlation != 'no': - # cross correlation and/or spatial correlation after S&H + elif (oq.spatial_correlation_model or + oq.cross_imt_correlation_model): + # spatial and cross-IMT correlation after Silva and Horspool gmf_dict = {'kind': 'Silva&Horspool', - 'spatialcorr': oq.spatial_correlation, - 'crosscorr': oq.cross_correlation, + 'spatial_model': + oq.get_spatial_correlation_model(), + 'cross_imt_model': + oq.get_cross_imt_correlation_model(), 'cholesky_limit': oq.cholesky_limit} else: # no correlation required, basic calculation is faster diff --git a/openquake/calculators/event_based.py b/openquake/calculators/event_based.py index 6db3d4168e8b..448bfa0d83c7 100644 --- a/openquake/calculators/event_based.py +++ b/openquake/calculators/event_based.py @@ -190,15 +190,17 @@ def get_computer(cmaker, ebr, sites, sec_perils=(), ebr, sites, sites.complete.filtered(station_sids), station_data.loc[station_sids], observed_imts, cmaker, - oq.correl_model, oq.cross_correl, - oq.ground_motion_correlation_params, + oq.get_spatial_correlation_model(), + oq.get_cross_imt_correlation_model(), + oq.spatial_correlation_params, oq.number_of_ground_motion_fields, oq._amplifier, sec_perils) else: logging.warning('There are no stations!') return GmfComputer( - ebr, sites, cmaker, oq.correl_model, oq.cross_correl, + ebr, sites, cmaker, oq.get_spatial_correlation_model(), + oq.get_cross_imt_correlation_model(), oq._amplifier, sec_perils) diff --git a/openquake/calculators/postproc/compute_mrd.py b/openquake/calculators/postproc/compute_mrd.py index f055587048e7..daa22d47752c 100644 --- a/openquake/calculators/postproc/compute_mrd.py +++ b/openquake/calculators/postproc/compute_mrd.py @@ -20,7 +20,8 @@ import numpy from openquake.baselib import parallel from openquake.hazardlib.calc.mrd import calc_mean_rate_dist -from openquake.hazardlib import contexts, cross_correlation as cc +from openquake.hazardlib import contexts +from openquake.hazardlib.correlation_models.registry import get_model class Input(object): @@ -70,7 +71,7 @@ def main(dstore, imt1, imt2, cross_correlation, seed, meabins, sigbins, NB: this is meant to work only for parametric ruptures! """ - crosscorr = getattr(cc, cross_correlation)() + crosscorr = get_model(cross_correlation, 'cross_imt') oq = dstore['oqparam'] N = len(dstore['sitecol']) L1 = oq.imtls.size // len(oq.imtls) - 1 diff --git a/openquake/calculators/postproc/conditional_spectrum.py b/openquake/calculators/postproc/conditional_spectrum.py index 21ec39f98670..2cd509998616 100644 --- a/openquake/calculators/postproc/conditional_spectrum.py +++ b/openquake/calculators/postproc/conditional_spectrum.py @@ -175,9 +175,10 @@ def main(dstore): logging.warning('Conditional spectrum calculations are still ' 'experimental') oq = dstore['oqparam'] - if not oq.cross_correl: - raise InvalidFile("%(job_ini)s: you must specify cross_correlation" - % oq.inputs) + if not oq.get_cross_imt_correlation_model(): + raise InvalidFile( + "%(job_ini)s: you must specify cross_imt_correlation_model" + % oq.inputs) if not oq.imt_ref: raise InvalidFile("%(job_ini)s: you must specify imt_ref" % oq.inputs) if not oq.poes: diff --git a/openquake/calculators/tests/event_based_test.py b/openquake/calculators/tests/event_based_test.py index 7125e3e64973..2bcb42764dcf 100644 --- a/openquake/calculators/tests/event_based_test.py +++ b/openquake/calculators/tests/event_based_test.py @@ -642,7 +642,7 @@ def test_30(self): hazard_calculation_id=hc_id) def test_31(self): - # HM2018CorrelationModel with filtered site collection + # HeresiMiranda2019 with filtered site collection self.run_calc(case_31.__file__, 'job.ini', hazard_calculation_id='job_rup.ini', exports='csv') diff --git a/openquake/calculators/views.py b/openquake/calculators/views.py index 2a212927bc17..42352a39c79d 100644 --- a/openquake/calculators/views.py +++ b/openquake/calculators/views.py @@ -367,7 +367,7 @@ def view_params(token, dstore): 'rupture_mesh_spacing', 'complex_fault_mesh_spacing', 'width_of_mfd_bin', 'area_source_discretization', 'pointsource_distance', - 'ground_motion_correlation_model', 'minimum_intensity', + 'spatial_correlation_model', 'minimum_intensity', 'random_seed', 'master_seed', 'ses_seed'] if 'risk' in oq.calculation_mode: params.append('avg_losses') diff --git a/openquake/commands/shakemap2gmfs.py b/openquake/commands/shakemap2gmfs.py index a8b76e8f1376..804e6ff87bd1 100644 --- a/openquake/commands/shakemap2gmfs.py +++ b/openquake/commands/shakemap2gmfs.py @@ -26,7 +26,9 @@ # see qa_tests_data/scenario/case_21 def main(id, site_model='', *, num_gmfs: int = 1, random_seed: int = 42, - trunclevel: float = 3, spatialcorr='yes', crosscorr='yes', + trunclevel: float = 3, spatial_correlation_model='', + spatial_correlation_params='', cross_imt_correlation_model='', + cross_imt_correlation_params='', cholesky_limit: int = 10_000, imt_mode='warn'): """ Given a shakemap ID and a path to a site_model.csv file build a @@ -46,6 +48,12 @@ def main(id, site_model='', *, num_gmfs: int = 1, random_seed: int = 42, calculation_mode='scenario', random_seed=str(random_seed), inputs={'job_ini': ''}) + if spatial_correlation_model: + param['spatial_correlation_model'] = spatial_correlation_model + param['spatial_correlation_params'] = spatial_correlation_params + if cross_imt_correlation_model: + param['cross_imt_correlation_model'] = cross_imt_correlation_model + param['cross_imt_correlation_params'] = cross_imt_correlation_params if site_model: param['inputs']['site_model'] = [os.path.abspath(site_model)] else: @@ -68,8 +76,9 @@ def main(id, site_model='', *, num_gmfs: int = 1, random_seed: int = 42, calc.datastore['sitecol'] = sitecol calc.datastore['full_lt'] = logictree.FullLogicTree.fake() gmfdic = {'kind': 'Silva&Horspool', - 'spatialcorr': spatialcorr, - 'crosscorr': crosscorr, + 'spatial_model': oq.get_spatial_correlation_model(), + 'cross_imt_model': + oq.get_cross_imt_correlation_model(), 'cholesky_limit': cholesky_limit} store_gmfs(calc, sitecol, shakemap, gmfdic) gmv = float(calc.datastore.read_df('gmf_data')[imts[0]].max()) @@ -82,7 +91,9 @@ def main(id, site_model='', *, num_gmfs: int = 1, random_seed: int = 42, main.num_gmfs = 'Number of GMFs to generate' main.random_seed = 'Random seed to use' main.trunclevel = 'Truncation level' -main.spatialcorr = 'Spatial correlation' -main.crosscorr = 'Cross correlation among IMTs' +main.spatial_correlation_model = 'Spatial correlation model name' +main.spatial_correlation_params = 'Spatial model parameter dictionary' +main.cross_imt_correlation_model = 'Cross-IMT correlation model name' +main.cross_imt_correlation_params = 'Cross-IMT model parameter dictionary' main.cholesky_limit = 'Cholesky Limit' main.imt_mode = 'if "strict", raise an error in case expected imts are missing' diff --git a/openquake/commands/tests/commands_test.py b/openquake/commands/tests/commands_test.py index ee5878f80867..191271fc51e1 100644 --- a/openquake/commands/tests/commands_test.py +++ b/openquake/commands/tests/commands_test.py @@ -656,8 +656,7 @@ def test_shakemap2gmfs(self): with chdir(os.path.dirname(case_25.__file__)): with redirect_stdout(io.StringIO()) as out: sap.runline('openquake.commands shakemap2gmfs usp0006dv8 ' - 'site_model_uniform_grid_rock.csv -n 1 -t 0 ' - '--spatialcorr no -c no') + 'site_model_uniform_grid_rock.csv -n 1 -t 0') got = out.getvalue() assert f'gmv={exp}' in got diff --git a/openquake/commonlib/oqvalidation.py b/openquake/commonlib/oqvalidation.py index 81d47dfdbc13..63b60dc3cdc5 100644 --- a/openquake/commonlib/oqvalidation.py +++ b/openquake/commonlib/oqvalidation.py @@ -38,7 +38,11 @@ DictArray, AccumDict, engine_version, count_lines) from openquake.hazardlib.imt import from_string, sort_by_imt, sec_imts from openquake.hazardlib import shakemap, retperiods -from openquake.hazardlib import correlation, cross_correlation, stats, calc +from openquake.hazardlib import stats, calc +from openquake.hazardlib.correlation_models.base import ( + TruncatedCrossIMTCorrelationModel) +from openquake.hazardlib.correlation_models.registry import ( + get_model, get_model_class) from openquake.hazardlib import valid, InvalidFile, site from openquake.hazardlib.gsim_lt import GsimLogicTree, ImtWeight from openquake.sep.classes import SecondaryPeril @@ -217,13 +221,16 @@ Example: *countries = ITA*. Default: () -cross_correlation: - When used in Conditional Spectrum calculation is the name of a cross - correlation class (i.e. "BakerJayaram2008"). - When used in ShakeMap calculations the valid choices are "yes", "no" "full", - same as for *spatial_correlation*. - Example: *cross_correlation = no*. - Default: "yes" +cross_imt_correlation_model: + Name of a registered cross-IMT correlation model. It is used consistently + by regular and ShakeMap calculations. + Example: *cross_imt_correlation_model = BakerJayaram2008*. + Default: empty, meaning no cross-IMT correlation + +cross_imt_correlation_params: + Parameters passed to the cross-IMT correlation model constructor. + Example: *cross_imt_correlation_params = {}*. + Default: empty dictionary siteid: Used in combination with sites to give an 8 character name to each site. @@ -334,14 +341,15 @@ Example: *exports = csv, rst*. Default: empty list -ground_motion_correlation_model: - Enable ground motion correlation. - Example: *ground_motion_correlation_model = JB2009*. +spatial_correlation_model: + Name of a registered spatial correlation model. It is used consistently + by regular and ShakeMap calculations. + Example: *spatial_correlation_model = JayaramBaker2009*. Default: None -ground_motion_correlation_params: - To be used together with ground_motion_correlation_model. - Example: *ground_motion_correlation_params = {"vs30_clustering": False}*. +spatial_correlation_params: + Parameters passed to the spatial correlation model constructor. + Example: *spatial_correlation_params = {"vs30_clustering": False}*. Default: empty dictionary ground_motion_fields: @@ -831,11 +839,6 @@ source_nodes: INTERNAL -spatial_correlation: - Used in the ShakeMap calculator. The choics are "yes", "no" and "full". - Example: *spatial_correlation = full*. - Default: "yes" - specific_assets: INTERNAL @@ -933,7 +936,6 @@ """ % __version__ PSDIST = float(config.performance.pointsource_distance) -GROUND_MOTION_CORRELATION_MODELS = ['JB2009', 'HM2018'] TWO16 = 2 ** 16 # 65536 TWO32 = 2 ** 32 U16 = numpy.uint16 @@ -1075,7 +1077,13 @@ class OqParam(valid.ParamSet): ALIASES = {'individual_curves': 'individual_rlzs', 'quantile_hazard_curves': 'quantiles', 'mean_hazard_curves': 'mean', - 'max_hazard_curves': 'max'} + 'max_hazard_curves': 'max', + 'ground_motion_correlation_model': + 'spatial_correlation_model', + 'ground_motion_correlation_params': + 'spatial_correlation_params', + 'cross_correlation': 'cross_imt_correlation_model', + 'spatial_correlation': 'spatial_correlation_model'} hazard_imtls = {} override_vs30 = valid.Param(valid.positivefloats, ()) @@ -1113,7 +1121,9 @@ class OqParam(valid.ParamSet): conditional_loss_poes = valid.Param(valid.probabilities, []) continuous_fragility_discretization = valid.Param(valid.positiveint, 20) countries = valid.Param(valid.namelist, ()) - cross_correlation = valid.Param(valid.utf8_not_empty, 'yes') + cross_imt_correlation_model = valid.Param( + valid.NoneOr(valid.utf8_not_empty), None) + cross_imt_correlation_params = valid.Param(valid.dictionary, {}) cholesky_limit = valid.Param(valid.positiveint, 10_000) correlation_cutoff = valid.Param(valid.positivefloat, 2E-4) siteid = valid.Param(valid.base64names, ()) @@ -1136,9 +1146,9 @@ class OqParam(valid.ParamSet): exports = valid.Param(valid.export_formats, ()) extreme_gmv = valid.Param(valid.floatdict, {'default': numpy.inf}) gmf_max_gb = valid.Param(valid.positivefloat, .01) - ground_motion_correlation_model = valid.Param( - valid.NoneOr(valid.Choice(*GROUND_MOTION_CORRELATION_MODELS)), None) - ground_motion_correlation_params = valid.Param(valid.dictionary, {}) + spatial_correlation_model = valid.Param( + valid.NoneOr(valid.utf8_not_empty), None) + spatial_correlation_params = valid.Param(valid.dictionary, {}) ground_motion_fields = valid.Param(valid.boolean, True) gsim = valid.Param(valid.utf8, '[FromFile]') hazard_calculation_id = valid.Param(valid.NoneOr(valid.calculation), None) @@ -1249,7 +1259,6 @@ class OqParam(valid.ParamSet): soil_intensities = valid.Param(valid.positivefloats, None) source_id = valid.Param(valid.namelist, []) source_nodes = valid.Param(valid.namelist, []) - spatial_correlation = valid.Param(valid.Choice('yes', 'no', 'full'), 'yes') specific_assets = valid.Param(valid.namelist, []) split_sources = valid.Param(valid.boolean, True) split_time = valid.Param(valid.positiveint, None) @@ -2089,35 +2098,40 @@ def no_imls(self): """ return sum(sum(imls) for imls in self.imtls.values()) == 0 + def get_spatial_correlation_model(self): + """Return the configured spatial correlation model, if any.""" + if self.spatial_correlation_model is None: + return + return get_model( + self.spatial_correlation_model, 'spatial', + **self.spatial_correlation_params) + + def get_cross_imt_correlation_model(self): + """Return the configured cross-IMT correlation model, if any.""" + if self.cross_imt_correlation_model is None: + return + cls = get_model_class( + self.cross_imt_correlation_model, model_type='cross_imt') + params = self.cross_imt_correlation_params + if issubclass(cls, TruncatedCrossIMTCorrelationModel): + params = dict(params) + truncation_level = self.truncation_level_between + if truncation_level is None: + truncation_level = ( + getattr(self, 'truncation_level', None) or 99.) + params.setdefault('truncation_level', truncation_level) + return get_model( + self.cross_imt_correlation_model, 'cross_imt', **params) + @property def correl_model(self): - """ - Return a correlation object. See :mod:`openquake.hazardlib.correlation` - for more info. - """ - correl_name = self.ground_motion_correlation_model - if correl_name is None: # no correlation model - return - correl_model_cls = getattr( - correlation, '%sCorrelationModel' % correl_name) - return correl_model_cls(**self.ground_motion_correlation_params) + """Compatibility alias for the configured spatial model.""" + return self.get_spatial_correlation_model() @property def cross_correl(self): - """ - Return a cross correlation object (or None). See - :mod:`openquake.hazardlib.cross_correlation` for more info. - """ - try: - cls = getattr(cross_correlation, self.cross_correlation) - except AttributeError: - return None - if issubclass(cls, cross_correlation.CrossCorrelationBetween): - tlb = self.truncation_level_between - if tlb is None: - tlb = getattr(self, 'truncation_level', None) or 99. - return cls(tlb) - return cls() + """Compatibility alias for the configured cross-IMT model.""" + return self.get_cross_imt_correlation_model() @property def rupture_xml(self): @@ -2257,11 +2271,23 @@ def is_valid_shakemap(self): return True return self.hazard_calculation_id if self.shakemap_id else True + def is_valid_correlation_models(self): + """ + Correlation model names must be registered for the requested type + """ + models = ( + (self.spatial_correlation_model, 'spatial'), + (self.cross_imt_correlation_model, 'cross_imt')) + for name, model_type in models: + if name: + get_model_class(name, model_type) + return True + def is_valid_truncation_level(self): """ In presence of a correlation model the truncation level must be nonzero """ - if self.ground_motion_correlation_model: + if self.spatial_correlation_model: return self.truncation_level_within != 0 else: return True @@ -2337,12 +2363,24 @@ def is_valid_intensity_measure_types(self): `intensity_measure_types_and_levels` is set directly, `intensity_measure_types` must not be set. """ - if self.ground_motion_correlation_model: - for imt in self.imtls: - if not (imt.startswith('SA') or imt in ['PGA', 'PGV']): + models = ( + (self.spatial_correlation_model, 'spatial'), + (self.cross_imt_correlation_model, 'cross_imt')) + for name, model_type in models: + if not name: + continue + cls = get_model_class(name, model_type) + if cls.supported_imts is None: + continue + for imt_str in self.imtls: + imt_name = from_string(imt_str).name + if (imt_name == 'MMI' and + ('shakemap' in self.inputs or self.shakemap_id)): + continue + if imt_name not in cls.supported_imts: raise ValueError( 'Correlation model %s does not accept IMT=%s' % ( - self.ground_motion_correlation_model, imt)) + name, imt_str)) if self.risk_files: # IMTLs extracted from the risk files return (self.intensity_measure_types == '' and self.intensity_measure_types_and_levels is None) diff --git a/openquake/commonlib/readinput.py b/openquake/commonlib/readinput.py index dc06695b084e..ba4db25c911c 100644 --- a/openquake/commonlib/readinput.py +++ b/openquake/commonlib/readinput.py @@ -892,14 +892,14 @@ def get_gsim_lt(oqparam, trts=()): if len(oqparam.site_labels) > 1: logictree.GsimLogicTree.check_multiple(gsim_file, trts) gsim_lt = logictree.GsimLogicTree(gsim_file, trts or oqparam._trts) - gmfcorr = oqparam.correl_model + spatial_model = oqparam.get_spatial_correlation_model() for trt, gsims in gsim_lt.values.items(): for gsim in gsims: # NB: gsim.DEFINED_FOR_TECTONIC_REGION_TYPE can be != trt, # but it is not an error, it is actually the most common case! - if gmfcorr and (gsim.DEFINED_FOR_STANDARD_DEVIATION_TYPES == + if spatial_model and (gsim.DEFINED_FOR_STANDARD_DEVIATION_TYPES == {StdDev.TOTAL}) and not oqparam.with_betw_ratio: - raise CorrelationButNoInterIntraStdDevs(gmfcorr, gsim) + raise CorrelationButNoInterIntraStdDevs(spatial_model, gsim) imt_dep_w = any(len(branch.weight.dic) > 1 for branch in gsim_lt.branches) if oqparam.number_of_logic_tree_samples and imt_dep_w: logging.warning( diff --git a/openquake/commonlib/tests/oqvalidation_test.py b/openquake/commonlib/tests/oqvalidation_test.py index 2a113659d219..188de7c160be 100644 --- a/openquake/commonlib/tests/oqvalidation_test.py +++ b/openquake/commonlib/tests/oqvalidation_test.py @@ -191,12 +191,44 @@ def test_invalid_imt(self): sites='0.1 0.2', maximum_distance='400', truncation_level='3', - ground_motion_correlation_model='JB2009', + spatial_correlation_model='JayaramBaker2009', intensity_measure_types_and_levels=imtls, ).validate() self.assertEqual( str(ctx.exception), - f'Correlation model JB2009 does not accept IMT={imt}') + 'Correlation model JayaramBaker2009 does not accept IMT=' + f'{imt}') + + def test_correlation_parameter_aliases(self): + oq = OqParam( + calculation_mode='event_based', inputs=fakeinputs, + sites='0.1 0.2', maximum_distance='400', + truncation_level='3', + ground_motion_correlation_model='JB2009', + ground_motion_correlation_params=( + '{"vs30_clustering": False}'), + cross_correlation='GodaAtkinson2009') + assert oq.spatial_correlation_model == 'JB2009' + assert oq.spatial_correlation_params == { + 'vs30_clustering': False} + assert oq.cross_imt_correlation_model == 'GodaAtkinson2009' + + def test_cross_imt_model_uses_split_truncation_level(self): + oq = OqParam( + calculation_mode='event_based', inputs=fakeinputs, + sites='0.1 0.2', maximum_distance='400', + truncation_level='3', truncation_level_between='2', + cross_imt_correlation_model='GodaAtkinson2009') + model = oq.get_cross_imt_correlation_model() + assert model.truncation_level == 2 + + def test_shakemap_switches_are_not_model_names(self): + for value in ('yes', 'no', 'full'): + with self.subTest(value=value), self.assertRaises(KeyError): + OqParam( + calculation_mode='scenario', inputs=fakeinputs, + sites='0.1 0.2', truncation_level='3', + spatial_correlation=value).validate() def test_duplicated_levels(self): with self.assertRaises(ValueError) as ctx: diff --git a/openquake/hazardlib/__init__.py b/openquake/hazardlib/__init__.py index ef605f669a09..05ebdd8ba825 100644 --- a/openquake/hazardlib/__init__.py +++ b/openquake/hazardlib/__init__.py @@ -217,7 +217,7 @@ def __init__(self, hparams, extra, read_all=True): hparams.update(extra) M = len(hparams['imtls']) assert isinstance(hparams['maximum_distance'], IntegrationDistance) - hparams.setdefault('cross_correl', None) + hparams.setdefault('cross_imt_model', None) hparams.setdefault('number_of_logic_tree_samples', 0) hparams.setdefault('random_seed', 42) hparams.setdefault('ses_seed', 42) diff --git a/openquake/hazardlib/calc/cond_spectra.py b/openquake/hazardlib/calc/cond_spectra.py index bc89369c3712..d8b7cc27acb3 100644 --- a/openquake/hazardlib/calc/cond_spectra.py +++ b/openquake/hazardlib/calc/cond_spectra.py @@ -118,8 +118,9 @@ def get_cs_out(cmaker, ctxt, imti, imlsNP, tom, _c=None): # This is the output dictionary as explained above out = outdict(M, N, P, cmaker.gid.min(), cmaker.gid.max() + 1) imt_ref = cmaker.imts[imti] - rho = numpy.array([cmaker.cross_correl.get_correlation(imt_ref, imt) - for imt in cmaker.imts]) + rho = numpy.array([ + cmaker.cross_imt_model.rho(imt_ref, imt) + for imt in cmaker.imts]) for sid, imls in enumerate(imlsNP): ctx = ctxt[ctxt.sids == sid] mean_stds = cmaker.get_mean_stds([ctx]) # (4, G, M, U) diff --git a/openquake/hazardlib/calc/conditioned_gmfs.py b/openquake/hazardlib/calc/conditioned_gmfs.py index a46d2a729006..f9c10f0ee4cb 100644 --- a/openquake/hazardlib/calc/conditioned_gmfs.py +++ b/openquake/hazardlib/calc/conditioned_gmfs.py @@ -115,9 +115,14 @@ import pandas from openquake.baselib import performance from openquake.hazardlib.truncated_mvn import TruncatedMVN -from openquake.hazardlib import correlation, cross_correlation from openquake.hazardlib.calc.gmf import GmfComputer, TRUNCATION_THRESHOLD from openquake.hazardlib.const import StdDev +from openquake.hazardlib.correlation_models.cross_imt.baker_jayaram_2008 \ + import BakerJayaram2008 +from openquake.hazardlib.correlation_models.cross_imt.goda_atkinson_2009 \ + import GodaAtkinson2009 +from openquake.hazardlib.correlation_models.spatial.jayaram_baker_2009 \ + import JayaramBaker2009 from openquake.hazardlib.geo.geodetic import geodetic_distance U32 = numpy.uint32 @@ -189,15 +194,15 @@ class ConditionedGmfComputer(GmfComputer): :param cmaker: a :class:`openquake.hazardlib.gsim.base.ContextMaker` instance - :param correlation_model: + :param spatial_model: Instance of a spatial correlation model object. See - :mod:`openquake.hazardlib.correlation`. Can be ``None``, in which + :mod:`openquake.hazardlib.correlation_models`. Can be ``None``, in which case non-correlated ground motion fields are calculated. Correlation model is not used if ``truncation_level`` is zero. - :param cross_correl: - Instance of a cross correlation model object. See - :mod:`openquake.hazardlib.cross_correlation`. Can be ``None``, in which + :param cross_imt_model: + Instance of a cross-IMT correlation model object. See + :mod:`openquake.hazardlib.correlation_models`. Can be ``None``, in which case non-cross-correlated ground motion fields are calculated. :param amplifier: @@ -210,18 +215,39 @@ class ConditionedGmfComputer(GmfComputer): """ def __init__( self, rupture, sitecol, station_sitecol, station_data, - observed_imts, cmaker, spatial_correl=None, - cross_correl_between=None, ground_motion_correlation_params=None, - number_of_ground_motion_fields=1, amplifier=None, sec_perils=()): + observed_imts, cmaker, spatial_model=None, + cross_imt_model=None, spatial_correlation_params=None, + number_of_ground_motion_fields=1, amplifier=None, sec_perils=(), + **legacy): + aliases = { + 'spatial_correl': 'spatial_model', + 'cross_correl_between': 'cross_imt_model', + 'ground_motion_correlation_params': + 'spatial_correlation_params'} + values = { + 'spatial_model': spatial_model, + 'cross_imt_model': cross_imt_model, + 'spatial_correlation_params': spatial_correlation_params} + for old_name, new_name in aliases.items(): + if old_name in legacy: + if values[new_name] is not None: + raise TypeError(f'Pass only {new_name}') + values[new_name] = legacy.pop(old_name) + if legacy: + raise TypeError('Unknown arguments: %s' % sorted(legacy)) + spatial_model = values['spatial_model'] + cross_imt_model = values['cross_imt_model'] + spatial_correlation_params = ( + values['spatial_correlation_params'] or {}) assert len(station_data) == len(station_sitecol), ( len(station_data), len(station_sitecol)) GmfComputer.__init__( self, rupture=rupture, sitecol=sitecol, cmaker=cmaker, - correlation_model=spatial_correl, - cross_correl=cross_correl_between, + spatial_model=spatial_model, + cross_imt_model=cross_imt_model, amplifier=amplifier, sec_perils=sec_perils) - clust = ground_motion_correlation_params.get("vs30_clustering", True) + clust = spatial_correlation_params.get("vs30_clustering", True) self.rupture = rupture # Target IMT must be PGA or SA @@ -231,9 +257,9 @@ def __init__( self.inp = Input( sitecol, station_sitecol, target_imts, observed_imts, station_data, - spatial_correl or correlation.JB2009CorrelationModel(clust), - cross_correl_between or cross_correlation.GodaAtkinson2009(), - cross_correlation.BakerJayaram2008()) + spatial_model or JayaramBaker2009(clust), + cross_imt_model or GodaAtkinson2009(), + BakerJayaram2008()) def _compute_mvn(self, mu_Y, cov_WY_WY, cov_BY_BY, E): rng = numpy.random.default_rng(self.seed) @@ -284,9 +310,9 @@ class Input: imts_Y: list = () imts_D: list = () stations: pandas.DataFrame = () - spatial_correl: object = 0 - cross_correl_between: object = 0 - cross_correl_within: object = 0 + spatial_model: object = 0 + between_event_cross_imt_model: object = 0 + within_event_cross_imt_model: object = 0 @dataclass @@ -410,7 +436,7 @@ def createD(g, m, target_imt, inp, mean_stds_D, DD): t.phi_D = phi_D.flatten() cov_WD_WD = compute_spatial_cross_covariance_matrix( - inp.spatial_correl, inp.cross_correl_within, DD, + inp.spatial_model, inp.within_event_cross_imt_model, DD, t.conditioning_imts, t.conditioning_imts, t.phi_D, t.phi_D) # Add on the additional variance of the residuals @@ -433,7 +459,7 @@ def createD(g, m, target_imt, inp, mean_stds_D, DD): # requiring the computation of the covariance matrix Σ_HD_HD, which is # just the matrix of cross-correlations for the observed IMTs, since # H is the normalized between-event residual - t.corr_HD_HD = inp.cross_correl_between._get_correlation_matrix( + t.corr_HD_HD = inp.between_event_cross_imt_model.correlation_matrix( t.bracketed_imts) return t @@ -458,19 +484,19 @@ def compute_distance_matrix(sites1, sites2): # called only by compute_spatial_cross_covariance_matrix def _compute_spatial_cross_correlation_matrix( - imt_1, imt_2, spatial_correl, cross_correl_within, distance_matrix): + imt_1, imt_2, spatial_model, cross_imt_model, distance_matrix): if imt_1 == imt_2: # since we have a single IMT, there are no cross-correlation terms - return spatial_correl._get_correlation_matrix(distance_matrix, imt_1) - matrix1 = spatial_correl._get_correlation_matrix(distance_matrix, imt_1) - matrix2 = spatial_correl._get_correlation_matrix(distance_matrix, imt_2) + return spatial_model.correlation_matrix(distance_matrix, imt_1) + matrix1 = spatial_model.correlation_matrix(distance_matrix, imt_1) + matrix2 = spatial_model.correlation_matrix(distance_matrix, imt_2) spatial_correlation_matrix = numpy.maximum(matrix1, matrix2) - cross_corr_coeff = cross_correl_within.get_correlation(imt_1, imt_2) + cross_corr_coeff = cross_imt_model.rho(imt_1, imt_2) return spatial_correlation_matrix * F32(cross_corr_coeff) def compute_spatial_cross_covariance_matrix( - spatial_correl, cross_correl_within, distance_matrix, + spatial_model, cross_imt_model, distance_matrix, imts1, imts2, stddev1, stddev2): # The correlation structure for IMs of differing types at differing # locations can be reasonably assumed as Markovian in nature, and we @@ -480,7 +506,7 @@ def compute_spatial_cross_covariance_matrix( # between sites m and n. Can be refactored down the line to support direct # spatial cross-correlation models rho = [[_compute_spatial_cross_correlation_matrix( - imt_1, imt_2, spatial_correl, cross_correl_within, distance_matrix) + imt_1, imt_2, spatial_model, cross_imt_model, distance_matrix) for imt_2 in imts2] for imt_1 in imts1] covariance = numpy.block(rho) covariance *= stddev1.astype(F32)[:, numpy.newaxis] @@ -564,7 +590,7 @@ def get_mu_tau_phi(self, m, target_imt, monitor, compute_covs=True): # (nsites, nstations) and (nstations, nsites) respectively with monitor.shared['YD'] as YD: cov_WY_WD = compute_spatial_cross_covariance_matrix( - inp.spatial_correl, inp.cross_correl_within, YD, + inp.spatial_model, inp.within_event_cross_imt_model, YD, [t.imt], t.conditioning_imts, phi_Y, t.phi_D) # Compute the regression coefficient matrix [cov_WY_WD × cov_WD_WD_inv] @@ -588,14 +614,14 @@ def _compute_target_covs( """Compute the covariance matrices required for random fields.""" with monitor.shared['DY'] as DY: cov_WD_WY = compute_spatial_cross_covariance_matrix( - inp.spatial_correl, inp.cross_correl_within, DY, + inp.spatial_model, inp.within_event_cross_imt_model, DY, t.conditioning_imts, [t.imt], t.phi_D, phi_Y) # This is the dominant piece, both in time and memory. with (monitor.shared['YY'] as YY, monitor('computing cov_Y_Y', measuremem=True)): cov_WY_WY = compute_spatial_cross_covariance_matrix( - inp.spatial_correl, inp.cross_correl_within, YY, + inp.spatial_model, inp.within_event_cross_imt_model, YY, [t.imt], [t.imt], phi_Y, phi_Y) # Conditioned within-event covariance, clipped to zero. diff --git a/openquake/hazardlib/calc/gmf.py b/openquake/hazardlib/calc/gmf.py index 52dc258d5a9b..9a94366fcf4a 100644 --- a/openquake/hazardlib/calc/gmf.py +++ b/openquake/hazardlib/calc/gmf.py @@ -27,7 +27,8 @@ from openquake.baselib.performance import Monitor, compile from openquake.hazardlib.const import StdDev from openquake.hazardlib.source.rupture import EBRupture, get_eid_rlz -from openquake.hazardlib.cross_correlation import NoCrossCorrelation +from openquake.hazardlib.correlation_models.cross_imt.no_cross_correlation \ + import NoCrossCorrelation from openquake.hazardlib.contexts import ContextMaker, FarAwayRupture from openquake.hazardlib.imt import from_string @@ -178,15 +179,15 @@ class GmfComputer(object): :param cmaker: a :class:`openquake.hazardlib.gsim.base.ContextMaker` instance - :param correlation_model: + :param spatial_model: Instance of a spatial correlation model object. See - :mod:`openquake.hazardlib.correlation`. Can be ``None``, in which + :mod:`openquake.hazardlib.correlation_models`. Can be ``None``, in which case non-correlated ground motion fields are calculated. Correlation model is not used if ``truncation_level`` is zero. - :param cross_correl: - Instance of a cross correlation model object. See - :mod:`openquake.hazardlib.cross_correlation`. Can be ``None``, in which + :param cross_imt_model: + Instance of a cross-IMT correlation model object. See + :mod:`openquake.hazardlib.correlation_models`. Can be ``None``, in which case non-cross-correlated ground motion fields are calculated. :param amplifier: @@ -208,8 +209,19 @@ class GmfComputer(object): # a matrix of size (M, N, E) is returned, where M is the number of # IMTs, N the number of affected sites and E the number of events. The # seed is extracted from the underlying rupture. - def __init__(self, rupture, sitecol, cmaker, correlation_model=None, - cross_correl=None, amplifier=None, sec_perils=()): + def __init__(self, rupture, sitecol, cmaker, spatial_model=None, + cross_imt_model=None, amplifier=None, sec_perils=(), + **legacy): + if 'correlation_model' in legacy: + if spatial_model is not None: + raise TypeError('Pass only spatial_model') + spatial_model = legacy.pop('correlation_model') + if 'cross_correl' in legacy: + if cross_imt_model is not None: + raise TypeError('Pass only cross_imt_model') + cross_imt_model = legacy.pop('cross_correl') + if legacy: + raise TypeError('Unknown arguments: %s' % sorted(legacy)) if len(sitecol) == 0: raise ValueError('No sites') elif len(cmaker.imtls) == 0: @@ -220,7 +232,7 @@ def __init__(self, rupture, sitecol, cmaker, correlation_model=None, self.imts = [from_string(imt) for imt in cmaker.imtls] self.cmaker = cmaker self.gsims = sorted(cmaker.gsims) - self.correlation_model = correlation_model + self.spatial_model = spatial_model self.amplifier = amplifier self.sec_perils = sec_perils self.ebrupture = rupture @@ -232,9 +244,9 @@ def __init__(self, rupture, sitecol, cmaker, correlation_model=None, raise FarAwayRupture [self.ctx] = ctxs self.N = len(self.ctx) - if correlation_model: # store the filtered sitecol + if spatial_model: # store the filtered sitecol self.sites = sitecol.complete.filtered(self.ctx.sids) - self.cross_correl = cross_correl or NoCrossCorrelation( + self.cross_imt_model = cross_imt_model or NoCrossCorrelation( cmaker.truncation_level_between) self.within_dist = NoCrossCorrelation( cmaker.truncation_level_within).distribution @@ -245,6 +257,24 @@ def __init__(self, rupture, sitecol, cmaker, correlation_model=None, if imt == 'MMI': self.mmi_index = m + @property + def correlation_model(self): + """Compatibility alias for :attr:`spatial_model`.""" + return self.spatial_model + + @correlation_model.setter + def correlation_model(self, model): + self.spatial_model = model + + @property + def cross_correl(self): + """Compatibility alias for :attr:`cross_imt_model`.""" + return self.cross_imt_model + + @cross_correl.setter + def cross_correl(self, model): + self.cross_imt_model = model + def init_eid_rlz_sig_eps(self): """ Initialize the attributes eid, rlz, sig, eps with shapes E, E, EM, EM @@ -386,7 +416,7 @@ def compute_all(self, MNE=None, cmon=Monitor(), umon=Monitor()): self.between_eps[idxs] = 0. else: self.between_eps[idxs] = \ - self.cross_correl.get_inter_eps( + self.cross_imt_model.get_inter_eps( self.imts, E, self.rng).T mean = [] for m, imt in enumerate(self.imts): @@ -438,7 +468,7 @@ def _compute(self, mean_stds, m, imt, gsim, within_eps, idxs): if (self.tlw <= TRUNCATION_THRESHOLD and self.tlb <= TRUNCATION_THRESHOLD): # for zero between/within truncation there is only mean, no stds - if self.correlation_model: + if self.spatial_model: raise ValueError('truncation_level_within=0 requires ' 'no correlation model') gmf = exp(mean, im != 'MMI')[:, np.newaxis].repeat( @@ -448,9 +478,9 @@ def _compute(self, mean_stds, m, imt, gsim, within_eps, idxs): # to compute mean and total standard deviation at the sites # of interest. # In this case, we also assume no correlation model is used. - if self.correlation_model: + if self.spatial_model: raise CorrelationButNoInterIntraStdDevs( - self.correlation_model, gsim) + self.spatial_model, gsim) gmf = exp(mean[:, np.newaxis] + sig[:, np.newaxis] * within_eps, im != 'MMI') self.sig[idxs, m] = np.nan @@ -460,8 +490,8 @@ def _compute(self, mean_stds, m, imt, gsim, within_eps, idxs): # a[:, newaxis] * b = [[1 2] [6 8]] which is the expected result; # otherwise one would get multiplication by column [[1 4] [3 8]] within_res = phi[:, np.newaxis] * within_eps # shape (N, E) - if self.correlation_model is not None: - within_res = self.correlation_model.apply_correlation( + if self.spatial_model is not None: + within_res = self.spatial_model.apply_correlation( self.sites, imt, within_res, phi).astype(F32) between_res = tau[:, np.newaxis] * self.between_eps[idxs, m] # shape (N, 1) * E => (N, E) diff --git a/openquake/hazardlib/calc/mrd.py b/openquake/hazardlib/calc/mrd.py index a6f51ff1b799..fa45ded76fdf 100644 --- a/openquake/hazardlib/calc/mrd.py +++ b/openquake/hazardlib/calc/mrd.py @@ -59,7 +59,7 @@ def update_mrd(ctxt: numpy.recarray, cm, crosscorr, mrd, monitor=Monitor()): # Correlation matrix im1, im2 = cm.imtls imts = [from_string(im1), from_string(im2)] - corrm = crosscorr.get_cross_correlation_mtx(imts) + corrm = crosscorr.correlation_matrix(imts, dtype=numpy.float32) # Compute mean and standard deviation [mea, sig, _, _] = cm.get_mean_stds([ctxt]) @@ -227,7 +227,7 @@ def calc_mean_rate_dist(ctx, nsites, cmaker, crosscorr, imt1, imt2, cm = cmaker.restrict([imt1, imt2]) G = len(cm.gsims) len1 = len(cm.imtls[imt1]) - 1 - corrm = crosscorr.get_cross_correlation_mtx(cm.imts) + corrm = crosscorr.correlation_matrix(cm.imts, dtype=numpy.float32) mrd = numpy.zeros((len1, len1, nsites, G)) for sid in range(nsites): if method == 'direct': diff --git a/openquake/hazardlib/contexts.py b/openquake/hazardlib/contexts.py index 421fccdf9754..70637757a92d 100644 --- a/openquake/hazardlib/contexts.py +++ b/openquake/hazardlib/contexts.py @@ -197,7 +197,7 @@ class Oq(object): """ af = None impact = False - cross_correl = None + cross_imt_model = None mea_tau_phi = False split_sources = True keep_rupdata = True @@ -529,6 +529,15 @@ class ContextMaker(object): source_mb = 0 # set in build_dparam dt = 0 + @property + def cross_correl(self): + """Compatibility alias for :attr:`cross_imt_model`.""" + return self.cross_imt_model + + @cross_correl.setter + def cross_correl(self, model): + self.cross_imt_model = model + def __init__(self, trt, gsims, oq, monitor=Monitor(), extraparams=()): self.trt = trt if isinstance(oq, dict): @@ -536,12 +545,22 @@ def __init__(self, trt, gsims, oq, monitor=Monitor(), extraparams=()): param = oq oq = Oq(**param) self.mags = param.get('mags', ()) # list of strings %.2f - self.cross_correl = param.get('cross_correl') # cond_spectra_test + self.cross_imt_model = param.get( + 'cross_imt_model', param.get('cross_correl')) else: # OqParam param = vars(oq) param['reqv'] = oq.get_reqv() param['af'] = getattr(oq, 'af', None) - self.cross_correl = oq.cross_correl + try: + resolver = oq.get_cross_imt_correlation_model + except AttributeError: + self.cross_imt_model = getattr( + oq, 'cross_imt_model', None) + if self.cross_imt_model is None: + self.cross_imt_model = getattr( + oq, 'cross_correl', None) + else: + self.cross_imt_model = resolver() self.imtls = oq.imtls try: self.mags = oq.mags_by_trt[trt] diff --git a/openquake/hazardlib/correlation.py b/openquake/hazardlib/correlation.py index 1037a8430240..4b128783ed8e 100644 --- a/openquake/hazardlib/correlation.py +++ b/openquake/hazardlib/correlation.py @@ -2,255 +2,39 @@ # Copyright (C) 2012-2026 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. +# 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 +# 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 . -""" -Module :mod:`openquake.hazardlib.correlation` defines correlation models for -spatially-distributed ground-shaking intensities. -""" -import abc -import numpy - - -class BaseCorrelationModel(metaclass=abc.ABCMeta): - """ - Base class for correlation models for spatially-distributed ground-shaking - intensities. - """ - def apply_correlation(self, sites, imt, residuals, stddev_intra=0): - """ - Apply correlation to randomly sampled residuals. - - :param sites: - :class:`~openquake.hazardlib.site.SiteCollection` residuals were - sampled for. - :param imt: - Intensity measure type object, see :mod:`openquake.hazardlib.imt`. - :param residuals: - 2d numpy array of sampled residuals, where first dimension - represents sites (the length as ``sites`` parameter) and - second one represents different realizations (samples). - :param stddev_intra: - Intra-event standard deviation array (phi). Different sites do - not necessarily have the same intra-event standard deviation. - :returns: - Array of the same structure and semantics as ``residuals`` - but with correlations applied. - - NB: the correlation matrix is cached. It is computed only once - per IMT for the complete site collection and then the portion - corresponding to the sites is multiplied by the residuals. - """ - # intra-event residual for a single relization is a product - # of lower-triangle decomposed correlation matrix and vector - # of N random numbers (where N is equal to number of sites). - # we need to do that multiplication once per realization - # with the same matrix and different vectors. - try: - corma = self.cache[imt] - except KeyError: - corma = self.get_lower_triangle_correlation_matrix( - sites.complete, imt) - self.cache[imt] = corma - # if N is the length of the complete site collection, then the - # correlation matrix has shape (N, N) and the residuals (N, s), - # where s is the number of samples - N = len(sites.complete) - n = len(sites) - if n < N: # filtered site collection - res = numpy.zeros((N, residuals.shape[1])) - res[sites.sids] = residuals - return (corma @ res)[sites.sids, :] # shape (n, s) - else: # complete site collection - return corma @ residuals # shape (N, s) - - -class JB2009CorrelationModel(BaseCorrelationModel): - """ - "Correlation model for spatially distributed ground-motion intensities" - by Nirmal Jayaram and Jack W. Baker. Published in Earthquake Engineering - and Structural Dynamics 2009; 38, pages 1687-1708. - - :param vs30_clustering: - Boolean value to indicate whether "Case 1" or "Case 2" from page 1700 - should be applied. ``True`` value means that Vs 30 values show or are - expected to show clustering ("Case 2"), ``False`` means otherwise. - """ - def __init__(self, vs30_clustering): - self.vs30_clustering = vs30_clustering - self.cache = {} # imt -> correlation model - - def _get_correlation_matrix(self, sites, imt): - return jbcorrelation(sites, imt, self.vs30_clustering) - - def get_lower_triangle_correlation_matrix(self, sites, imt): - """ - Get lower-triangle matrix as a result of Cholesky-decomposition - of correlation matrix. - - The resulting matrix should have zeros on values above - the main diagonal. - - The actual implementations of :class:`BaseCorrelationModel` interface - might calculate the matrix considering site collection and IMT (like - :class:`JB2009CorrelationModel` does) or might have it pre-constructed - for a specific site collection and IMT, in which case they will need - to make sure that parameters to this function match parameters that - were used to pre-calculate decomposed correlation matrix. - - :param sites: - :class:`~openquake.hazardlib.site.SiteCollection` to create - correlation matrix for. - :param imt: - Intensity measure type object, see :mod:`openquake.hazardlib.imt`. - """ - return numpy.linalg.cholesky(self._get_correlation_matrix(sites, imt)) - - -def jbcorrelation(sites_or_distances, imt, vs30_clustering=False): - """ - Returns the Jayaram-Baker correlation model. - - :param sites_or_distances: - SiteCollection instance o ristance matrix - :param imt: - Intensity Measure Type (PGA or SA) - :param vs30_clustering: - flag, defalt false - """ - if hasattr(sites_or_distances, 'mesh'): - distances = sites_or_distances.mesh.get_distance_matrix() - else: - distances = sites_or_distances - - # formulae are from page 1700 - period = 1.0 if imt.string == 'PGV' else imt.period - if period < 1: - if not vs30_clustering: - # case 1, eq. (17) - b = 8.5 + 17.2 * period - else: - # case 2, eq. (18) - b = 40.7 - 15.0 * period - else: - # both cases, eq. (19) - b = 22.0 + 3.7 * period - - # eq. (20) - return numpy.exp((- 3.0 / b) * distances) - - -class HM2018CorrelationModel(BaseCorrelationModel): - """ - "Uncertainty in intraevent spatial correlation of elastic pseudo- - acceleration spectral ordinates" - by Pablo Heresi and Eduardo Miranda. Submitted for possible publication - in Bulletin of Earthquake Engineering, 2018. - - :param uncertainty_multiplier: - Value to be multiplied by the uncertainty in the correlation parameter - beta. If uncertainty_multiplier = 0 (default), the median value is - used as a constant value. - """ - def __init__(self, uncertainty_multiplier=0): - self.uncertainty_multiplier = uncertainty_multiplier - self.distance_matrix = {} - self.cache = {} - - def _get_correlation_matrix(self, sites, imt): - return hmcorrelation(sites, imt, self.uncertainty_multiplier) - - def apply_correlation(self, sites, imt, residuals, stddev_intra): - """ - Apply correlation to randomly sampled residuals - """ - # TODO: the case of filtered sites is probably managed incorrectly - # NB: this is SLOW and we cannot use the cache as in JB2009 because - # we are not using the complete site collection - nsites = len(sites) - assert len(residuals) == len(stddev_intra) == nsites - D = numpy.diag(stddev_intra) # phi as a diagonal matrix - - if self.uncertainty_multiplier == 0: # No uncertainty - - # residuals were sampled from a normal distribution with - # stddev_intra standard deviation. 'residuals_norm' are residuals - # normalized, sampled from a standard normal distribution. - # For this, every row of 'residuals' (every site) is divided by its - # corresponding standard deviation element. - residuals_norm = residuals / stddev_intra[:, None] - - # Lower diagonal of the Cholesky decomposition - # Note that instead of computing the whole correlation matrix - # corresponding to sites.complete, here we compute only the - # correlation matrix corresponding to sites - cormaLow = numpy.linalg.cholesky( - D @ self._get_correlation_matrix(sites, imt) @ D) - - # Apply correlation - return cormaLow @ residuals_norm - - else: # Variability (uncertainty) is included - nsim = residuals.shape[1] - - # Re-sample all the residuals - residuals_correlated = residuals * 0 - for isim in range(0, nsim): - # FIXME: the seed is not set! - corma = self._get_correlation_matrix(sites, imt) - # NB: corma is different at each loop since contains randomicity - residuals_correlated[0:, isim] = ( - numpy.random.multivariate_normal( - numpy.zeros(nsites), D @ corma @ D, 1)) - - return residuals_correlated - - -def hmcorrelation(sites_or_distances, imt, uncertainty_multiplier=0): - """ - Returns the Heresi-Miranda correlation model. - - :param sites_or_distances: - SiteCollection instance o distance matrix - :param imt: - Intensity Measure Type (PGA or SA) - :param uncertainty_multiplier: - Value to be multiplied by the uncertainty in the correlation parameter - beta. If uncertainty_multiplier = 0 (default), the median value is - used as a constant value. - """ - if hasattr(sites_or_distances, 'mesh'): - distances = sites_or_distances.mesh.get_distance_matrix() - else: - distances = sites_or_distances - - period = imt.period - - # Eq. (9) - if period < 1.37: - Med_b = 4.231 * period * period - 5.180 * period + 13.392 - else: - Med_b = 0.140 * period * period - 2.249 * period + 17.050 - - # Eq. (10) - Std_b = (4.63e-3 * period*period + 0.028 * period + 0.713) - - # Obtain realization of b - if uncertainty_multiplier == 0: - beta = Med_b - else: - beta = numpy.random.lognormal( - numpy.log(Med_b), Std_b * uncertainty_multiplier) - - # Eq. (8) - res = numpy.exp(-numpy.power((distances / beta), 0.55)) - return res +# along with this program. If not, see . +"""Compatibility imports for spatial ground-motion correlation models.""" + +# DEPRECATION +# ----------- +# New code should import spatial models from +# ``openquake.hazardlib.correlation_models.spatial``. This module remains +# available during the correlation-framework migration so existing imports, +# job files, and downstream libraries keep working without changed results. +# Remove only after the documented deprecation window and migration of all +# internal callers. + +from openquake.hazardlib.correlation_models.base import ( + SpatialCorrelationModel as BaseCorrelationModel) +from openquake.hazardlib.correlation_models.spatial.heresi_miranda_2019 import ( + HeresiMiranda2019, _correlation_matrix as hmcorrelation) +from openquake.hazardlib.correlation_models.spatial.jayaram_baker_2009 import ( + JayaramBaker2009, _correlation_matrix as jbcorrelation) + + +JB2009CorrelationModel = JayaramBaker2009 +HM2018CorrelationModel = HeresiMiranda2019 + +__all__ = [ + 'BaseCorrelationModel', 'HM2018CorrelationModel', + 'JB2009CorrelationModel', 'hmcorrelation', 'jbcorrelation'] diff --git a/openquake/hazardlib/correlation_models/__init__.py b/openquake/hazardlib/correlation_models/__init__.py new file mode 100644 index 000000000000..117c37a2d568 --- /dev/null +++ b/openquake/hazardlib/correlation_models/__init__.py @@ -0,0 +1 @@ +"""Ground-motion correlation model framework.""" diff --git a/openquake/hazardlib/correlation_models/base.py b/openquake/hazardlib/correlation_models/base.py new file mode 100644 index 000000000000..0cf4ca329798 --- /dev/null +++ b/openquake/hazardlib/correlation_models/base.py @@ -0,0 +1,289 @@ +# The Hazard Library +# Copyright (C) 2012-2026 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 . +"""Base interfaces shared by ground-motion correlation models.""" + +from dataclasses import dataclass, field +from enum import Enum +from typing import Mapping, Protocol, runtime_checkable + +import numpy +from scipy import stats + +from openquake.hazardlib.correlation_utils import corr_clipped, cov_nearest +from openquake.hazardlib.truncated_mvn import TruncatedMVN + + +class ResidualComponent(str, Enum): + """Residual components for which a model can be calibrated.""" + + TOTAL = 'total' + WITHIN_EVENT = 'within' + BETWEEN_EVENT = 'between' + + +@dataclass(frozen=True) +class CorrelationContext: + """Optional predictors used by context-dependent correlation models.""" + + mag: float | None = None + rake: float | None = None + trt: str | None = None + region: str | None = None + values: Mapping[str, object] = field(default_factory=dict) + + +@runtime_checkable +class CorrelationFactor(Protocol): + """A factorization capable of applying correlation to samples.""" + + def apply(self, samples: numpy.ndarray) -> numpy.ndarray: + """Apply the factorization to standard-normal samples.""" + + +@dataclass(frozen=True) +class CholeskyFactor: + """Dense Cholesky factorization used by the default implementation.""" + + lower_triangle: numpy.ndarray + + def apply(self, samples): + return self.lower_triangle @ samples + + +class CorrelationModel: + """Common metadata and validation for all correlation models.""" + + name = '' + calibrated_component = None + supported_imts = None + imc = None + damping = None + required_context = () + + def validate(self): + """Validate model parameters after construction.""" + + def validate_imts(self, imts): + """Raise when an IMT is outside the model's declared scope.""" + if self.supported_imts is None: + return + unsupported = sorted({imt.name for imt in imts + if imt.name not in self.supported_imts}) + if unsupported: + raise ValueError( + f'{self.name or self.__class__.__name__} does not support ' + f'{", ".join(unsupported)}') + + def _get_component(self, component=None): + if component is None: + return self.calibrated_component + try: + component = ResidualComponent(component) + except ValueError as exc: + allowed = ', '.join(item.value for item in ResidualComponent) + raise ValueError( + f'Unknown residual component {component!r}; use {allowed}' + ) from exc + if (self.calibrated_component is not None and + component != self.calibrated_component): + raise ValueError( + f'{self.name or self.__class__.__name__} provides ' + f'{self.calibrated_component.value} correlation, not ' + f'{component.value}') + return component + + +class SpatialCrossIMTCorrelationModel(CorrelationModel): + """Correlation over a joint, IMT-major vector of sites and IMTs.""" + + def covariance(self, sites, imts, component=None, context=None): + """Return a covariance matrix with shape ``(M*N, M*N)``.""" + raise NotImplementedError + + def factor(self, sites, imts, component=None, context=None, + ensure_psd=True): + """Return the default dense factorization of :meth:`covariance`. + + The fast path attempts Cholesky decomposition directly. A covariance + repair is performed only when decomposition fails and ``ensure_psd`` + is true. Models with efficient structured factorizations should + override this method. + """ + covariance = self.covariance(sites, imts, component, context) + try: + lower_triangle = numpy.linalg.cholesky(covariance) + except numpy.linalg.LinAlgError: + if not ensure_psd: + raise + covariance = cov_nearest(covariance, threshold=1E-12) + lower_triangle = numpy.linalg.cholesky(covariance) + return CholeskyFactor(lower_triangle) + + +class SpatialCorrelationModel(SpatialCrossIMTCorrelationModel): + """Same-IMT spatial correlation over a collection of sites.""" + + def __init__(self): + self.cache = {} + + def correlation_matrix(self, sites, imt, component=None, context=None): + """Return an ``N x N`` same-IMT spatial correlation matrix.""" + self._get_component(component) + legacy = type(self)._get_correlation_matrix + if legacy is SpatialCorrelationModel._get_correlation_matrix: + raise NotImplementedError + return legacy(self, sites, imt) + + def _get_correlation_matrix(self, sites, imt): + implementation = type(self).correlation_matrix + if implementation is SpatialCorrelationModel.correlation_matrix: + raise NotImplementedError + return implementation(self, sites, imt) + + def get_lower_triangle_correlation_matrix(self, sites, imt): + """Return the dense Cholesky factor of the correlation matrix.""" + return numpy.linalg.cholesky( + self.correlation_matrix(sites, imt)) + + def apply_correlation(self, sites, imt, residuals, stddev_intra=0): + """Apply spatial correlation to sampled within-event residuals.""" + try: + lower_triangle = self.cache[imt] + except KeyError: + lower_triangle = self.get_lower_triangle_correlation_matrix( + sites.complete, imt) + self.cache[imt] = lower_triangle + num_complete = len(sites.complete) + if len(sites) < num_complete: + complete = numpy.zeros((num_complete, residuals.shape[1])) + complete[sites.sids] = residuals + return (lower_triangle @ complete)[sites.sids, :] + return lower_triangle @ residuals + + def covariance(self, sites, imts, component=None, context=None): + """Embed same-IMT matrices in IMT-major diagonal blocks.""" + num_sites = len(sites) + covariance = numpy.zeros( + (len(imts) * num_sites, len(imts) * num_sites)) + for imt_index, imt in enumerate(imts): + start = imt_index * num_sites + block = slice(start, start + num_sites) + covariance[block, block] = self.correlation_matrix( + sites, imt, component, context) + return covariance + + +class CrossIMTCorrelationModel(SpatialCrossIMTCorrelationModel): + """Cross-IMT correlation at a single site.""" + + def rho(self, from_imt, to_imt, component=None, context=None): + """Return the correlation between two IMTs.""" + self._get_component(component) + legacy = type(self).get_correlation + if legacy is CrossIMTCorrelationModel.get_correlation: + raise NotImplementedError + return legacy(self, from_imt, to_imt) + + def get_correlation(self, from_imt, to_imt): + """Compatibility alias for :meth:`rho`.""" + implementation = type(self).rho + if implementation is CrossIMTCorrelationModel.rho: + raise NotImplementedError + return implementation(self, from_imt, to_imt) + + def correlation_matrix(self, imts, component=None, context=None, + dtype=float): + """Return an ``M x M`` cross-IMT correlation matrix.""" + self._get_component(component) + matrix = numpy.zeros((len(imts), len(imts)), dtype) + for row, from_imt in enumerate(imts): + for col in range(row, len(imts)): + correlation = self.rho( + from_imt, imts[col], component, context) + matrix[row, col] = correlation + matrix[col, row] = correlation + return matrix + + def get_cross_correlation_mtx(self, imts): + """Compatibility alias returning the historical float32 matrix.""" + return self.correlation_matrix(imts, dtype=numpy.float32) + + def covariance(self, sites, imts, component=None, context=None): + """Embed cross-IMT matrices for each site in IMT-major ordering.""" + num_sites = len(sites) + num_imts = len(imts) + imt_correlation = self.correlation_matrix( + imts, component, context) + covariance = numpy.zeros( + (num_imts * num_sites, num_imts * num_sites)) + for site_index in range(num_sites): + indexes = [imt_index * num_sites + site_index + for imt_index in range(num_imts)] + covariance[numpy.ix_(indexes, indexes)] = imt_correlation + return covariance + + +class TruncatedCrossIMTCorrelationModel(CrossIMTCorrelationModel): + """Cross-IMT model able to sample truncated normal residuals.""" + + matrix_dtype = float + + def __init__(self, truncation_level=99.): + if truncation_level < 1E-9: + truncation_level = 1E-9 + self.truncation_level = truncation_level + self.distribution = stats.truncnorm( + -truncation_level, truncation_level) + self.cache = {} + + def _get_correlation_matrix(self, imts): + key = tuple(imts) + try: + return self.cache[key] + except KeyError: + matrix = self.correlation_matrix( + imts, dtype=self.matrix_dtype) + self.cache[key] = matrix + return matrix + + def get_inter_eps(self, imts, num_events, rng): + """Return an ``M x E`` matrix of correlated event terms.""" + matrix = self._get_correlation_matrix(imts) + return self._get_inter_eps_trunc_mvn(matrix, num_events, rng) + + def _get_inter_eps_trunc_mvn(self, matrix, num_events, rng): + num_imts = len(matrix) + mean = numpy.zeros(num_imts) + bounds = numpy.full(num_imts, self.truncation_level) + seed = int(rng.integers(0, numpy.iinfo(numpy.int32).max)) + correlation = numpy.array(matrix, copy=True) + min_eigenvalue = numpy.linalg.eigvalsh(correlation).min() + if (not numpy.isfinite(min_eigenvalue) or + min_eigenvalue < 1E-8): + correlation = corr_clipped(correlation, threshold=1E-8) + try: + samples = TruncatedMVN( + mean, correlation, -bounds, bounds, seed=seed + ).sample(num_events) + if numpy.isfinite(samples).all(): + return samples + except RuntimeError as exc: + if 'not positive semi-definite' not in str(exc): + raise + correlation = corr_clipped(correlation, threshold=1E-6) + return TruncatedMVN( + mean, correlation, -bounds, bounds, seed=seed + ).sample(num_events) diff --git a/openquake/hazardlib/correlation_models/cross_imt/__init__.py b/openquake/hazardlib/correlation_models/cross_imt/__init__.py new file mode 100644 index 000000000000..3b846fc3a6db --- /dev/null +++ b/openquake/hazardlib/correlation_models/cross_imt/__init__.py @@ -0,0 +1 @@ +"""Non-spatial cross-IMT correlation models.""" diff --git a/openquake/hazardlib/correlation_models/cross_imt/baker_cornell_2006.py b/openquake/hazardlib/correlation_models/cross_imt/baker_cornell_2006.py new file mode 100644 index 000000000000..da8b145525d5 --- /dev/null +++ b/openquake/hazardlib/correlation_models/cross_imt/baker_cornell_2006.py @@ -0,0 +1,56 @@ +# The Hazard Library +# Copyright (C) 2026 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 . +"""Baker and Cornell (2006) cross-IMT correlation model. + +References +---------- +Baker, J. W., and Cornell, C. A. (2006). Correlation of response spectral +values for multicomponent ground motions. Bulletin of the Seismological +Society of America, 96(1), 215-227. +https://doi.org/10.1785/0120050060 +""" + +import math + +from openquake.hazardlib.correlation_models.base import ( + CrossIMTCorrelationModel, ResidualComponent) +from openquake.hazardlib.correlation_models.registry import register_model + + +@register_model(description='Baker and Cornell (2006) SA correlation') +class BakerCornell2006(CrossIMTCorrelationModel): + """Total-residual spectral correlation by Baker and Cornell (2006).""" + + name = 'BakerCornell2006' + calibrated_component = ResidualComponent.TOTAL + # The historical ShakeMap implementation treats PGA and PGV as + # 0.05-second SA for this correlation calculation. + supported_imts = ('PGA', 'PGV', 'SA') + + def rho(self, from_imt, to_imt, component=None, context=None): + self._get_component(component) + if from_imt == to_imt: + return 1.0 + min_period = min(from_imt.period or 0.05, + to_imt.period or 0.05) + max_period = max(from_imt.period or 0.05, + to_imt.period or 0.05) + short_period = 1 if min_period < 0.189 else 0 + angle = math.pi / 2 - ( + 0.359 + 0.163 * short_period * + math.log(min_period / 0.189) + ) * math.log(max_period / min_period) + return 1 - math.cos(angle) diff --git a/openquake/hazardlib/correlation_models/cross_imt/baker_jayaram_2008.py b/openquake/hazardlib/correlation_models/cross_imt/baker_jayaram_2008.py new file mode 100644 index 000000000000..a76567b8a750 --- /dev/null +++ b/openquake/hazardlib/correlation_models/cross_imt/baker_jayaram_2008.py @@ -0,0 +1,72 @@ +# The Hazard Library +# Copyright (C) 2021-2026 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 . +"""Baker and Jayaram (2008) cross-IMT correlation model. + +References +---------- +Baker, J. W., and Jayaram, N. (2008). Correlation of Spectral Acceleration +Values from NGA Ground Motion Models. Earthquake Spectra, 24(1), 299-317. +https://doi.org/10.1193/1.2857544 +""" + +import numpy +from scipy import constants + +from openquake.hazardlib.correlation_models.base import ( + CrossIMTCorrelationModel, ResidualComponent) +from openquake.hazardlib.correlation_models.registry import register_model + + +@register_model( + 'BJ2008', + description='Baker and Jayaram (2008) cross-IMT correlation') +class BakerJayaram2008(CrossIMTCorrelationModel): + """Total-residual cross-IMT correlation for GMRotI50.""" + + name = 'BakerJayaram2008' + calibrated_component = ResidualComponent.TOTAL + supported_imts = ('PGA', 'SA') + imc = 'GMRotI50' + + def rho(self, from_imt, to_imt, component=None, context=None): + self._get_component(component) + from_period = from_imt.period + to_period = to_imt.period + if numpy.abs(from_period - to_period) < 1E-10: + return 1.0 + + min_period = min(from_period, to_period) + max_period = max(from_period, to_period) + c1 = 1 - numpy.cos( + constants.pi / 2 - 0.366 * numpy.log( + max_period / max(min_period, 0.109))) + c2 = 0.0 + if max_period < 0.2: + term1 = 1.0 - 1.0 / ( + 1.0 + numpy.exp(100.0 * max_period - 5.0)) + term2 = ((max_period - min_period) / + (max_period - 0.0099)) + c2 = 1 - 0.105 * term1 * term2 + c3 = c2 if max_period < 0.109 else c1 + c4 = c1 + 0.5 * (numpy.sqrt(c3) - c3) * ( + 1 + numpy.cos(constants.pi * min_period / 0.109)) + if max_period < 0.109: + return c2 + if min_period > 0.109: + return c1 + if max_period < 0.2: + return min(c2, c4) + return c4 diff --git a/openquake/hazardlib/correlation_models/cross_imt/bradley_2012.py b/openquake/hazardlib/correlation_models/cross_imt/bradley_2012.py new file mode 100644 index 000000000000..d10a025265ff --- /dev/null +++ b/openquake/hazardlib/correlation_models/cross_imt/bradley_2012.py @@ -0,0 +1,59 @@ +# The Hazard Library +# Copyright (C) 2021-2026 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 . +"""Bradley (2012) cross-IMT correlation model. + +References +---------- +Bradley, B. A. (2012). Empirical correlations between peak ground velocity +and spectrum-based intensity measures. Earthquake Spectra, 28(1), 17-35. +https://doi.org/10.1193/1.3675582 +""" + +import numpy + +from openquake.hazardlib.correlation_models.base import ( + ResidualComponent, TruncatedCrossIMTCorrelationModel) +from openquake.hazardlib.correlation_models.registry import register_model + + +@register_model(description='Bradley (2012) PGV and spectrum correlation') +class Bradley2012(TruncatedCrossIMTCorrelationModel): + """Total-residual correlation between PGV and spectrum-based IMTs.""" + + name = 'Bradley2012' + calibrated_component = ResidualComponent.TOTAL + supported_imts = ('PGV', 'PGA', 'SA') + + def rho(self, from_imt, to_imt, component=None, context=None): + self._get_component(component) + if from_imt == to_imt: + return 1 + if from_imt.string != 'PGV' and to_imt.string != 'PGV': + return 0 + period = (to_imt.period if from_imt.string == 'PGV' + else from_imt.period) + if period < 0.01: + return 0.733 + if period < 0.1: + a, b, c, d = 0.73, 0.54, 0.045, 1.8 + elif period < 0.75: + a, b, c, d = 0.54, 0.81, 0.28, 1.5 + elif period < 2.5: + a, b, c, d = 0.80, 0.76, 1.1, 3.0 + else: + a, b, c, d = 0.76, 0.70, 5.0, 3.2 + return ((a + b) / 2 - + (a - b) / 2 * numpy.tanh(d * numpy.log(period / c))) diff --git a/openquake/hazardlib/correlation_models/cross_imt/full_cross_correlation.py b/openquake/hazardlib/correlation_models/cross_imt/full_cross_correlation.py new file mode 100644 index 000000000000..8485eaf93541 --- /dev/null +++ b/openquake/hazardlib/correlation_models/cross_imt/full_cross_correlation.py @@ -0,0 +1,38 @@ +# The Hazard Library +# Copyright (C) 2021-2026 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 . +"""Fully correlated cross-IMT residual sampling.""" + +import numpy + +from openquake.hazardlib.correlation_models.base import ( + ResidualComponent, TruncatedCrossIMTCorrelationModel) +from openquake.hazardlib.correlation_models.registry import register_model + + +@register_model(description='Identical residuals across IMTs') +class FullCrossCorrelation(TruncatedCrossIMTCorrelationModel): + """Represent perfect cross-IMT correlation.""" + + name = 'FullCrossCorrelation' + calibrated_component = ResidualComponent.BETWEEN_EVENT + + def rho(self, from_imt, to_imt, component=None, context=None): + self._get_component(component) + return 1.0 + + def get_inter_eps(self, imts, num_events, rng): + residuals = self.distribution.rvs(num_events, rng) + return numpy.array([residuals for imt in imts]) diff --git a/openquake/hazardlib/correlation_models/cross_imt/goda_atkinson_2009.py b/openquake/hazardlib/correlation_models/cross_imt/goda_atkinson_2009.py new file mode 100644 index 000000000000..cb75750d1c42 --- /dev/null +++ b/openquake/hazardlib/correlation_models/cross_imt/goda_atkinson_2009.py @@ -0,0 +1,62 @@ +# The Hazard Library +# Copyright (C) 2021-2026 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 . +"""Goda and Atkinson (2009) cross-IMT correlation model. + +References +---------- +Goda, K., and Atkinson, G. M. (2009). Probabilistic characterization of +spatially correlated response spectra for earthquakes in Japan. Bulletin of +the Seismological Society of America, 99(5), 3003-3020. +https://doi.org/10.1785/0120090007 +""" + +import numpy + +from openquake.hazardlib.correlation_models.base import ( + ResidualComponent, TruncatedCrossIMTCorrelationModel) +from openquake.hazardlib.correlation_models.registry import register_model + + +@register_model( + 'GA2009', + description='Goda and Atkinson (2009) cross-IMT correlation') +class GodaAtkinson2009(TruncatedCrossIMTCorrelationModel): + """Between-event cross-IMT correlation by Goda and Atkinson (2009).""" + + name = 'GodaAtkinson2009' + calibrated_component = ResidualComponent.BETWEEN_EVENT + supported_imts = ('PGA', 'SA') + matrix_dtype = numpy.float32 + + def rho(self, from_imt, to_imt, component=None, context=None): + self._get_component(component) + if from_imt == to_imt: + return 1.0 + + period1 = from_imt.period or 0.05 + period2 = to_imt.period or 0.05 + min_period = min(period1, period2) + max_period = max(period1, period2) + short_period = 1.0 if min_period < 0.25 else 0.0 + angle = numpy.pi / 2.0 - ( + 1.374 + 5.586 * short_period * + (min_period / max_period) ** 0.728 * + numpy.log10(min_period / 0.25) + ) * numpy.log10(max_period / min_period) + delta = 1.0 + numpy.cos( + -1.5 * numpy.log10(max_period / min_period)) + correlation = (1.0 - numpy.cos(angle) + delta) / 3.0 + return min(correlation, 1.0) diff --git a/openquake/hazardlib/correlation_models/cross_imt/no_cross_correlation.py b/openquake/hazardlib/correlation_models/cross_imt/no_cross_correlation.py new file mode 100644 index 000000000000..92639a19076a --- /dev/null +++ b/openquake/hazardlib/correlation_models/cross_imt/no_cross_correlation.py @@ -0,0 +1,38 @@ +# The Hazard Library +# Copyright (C) 2021-2026 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 . +"""Independent cross-IMT residual sampling.""" + +import numpy + +from openquake.hazardlib.correlation_models.base import ( + ResidualComponent, TruncatedCrossIMTCorrelationModel) +from openquake.hazardlib.correlation_models.registry import register_model + + +@register_model(description='Independent residuals across IMTs') +class NoCrossCorrelation(TruncatedCrossIMTCorrelationModel): + """Represent the absence of cross-IMT correlation.""" + + name = 'NoCrossCorrelation' + calibrated_component = ResidualComponent.BETWEEN_EVENT + + def rho(self, from_imt, to_imt, component=None, context=None): + self._get_component(component) + return from_imt == to_imt + + def get_inter_eps(self, imts, num_events, rng): + return numpy.array([ + self.distribution.rvs(num_events, rng) for imt in imts]) diff --git a/openquake/hazardlib/correlation_models/registry.py b/openquake/hazardlib/correlation_models/registry.py new file mode 100644 index 000000000000..486365f836c4 --- /dev/null +++ b/openquake/hazardlib/correlation_models/registry.py @@ -0,0 +1,132 @@ +# The Hazard Library +# Copyright (C) 2026 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 . +"""Registry for ground-motion correlation models.""" + +from dataclasses import dataclass + +from openquake.baselib.general import import_all +from openquake.hazardlib.correlation_models.base import ( + CorrelationModel, CrossIMTCorrelationModel, SpatialCorrelationModel, + SpatialCrossIMTCorrelationModel) + + +@dataclass(frozen=True) +class ModelSpec: + """Registration metadata for a correlation model.""" + + name: str + cls: type[CorrelationModel] + aliases: tuple[str, ...] + model_type: str + description: str + + @property + def calibrated_component(self): + return self.cls.calibrated_component + + @property + def supported_imts(self): + return self.cls.supported_imts + + @property + def imc(self): + return self.cls.imc + + @property + def damping(self): + return self.cls.damping + + @property + def required_context(self): + return self.cls.required_context + + +registry = {} +_specs = {} +_models_loaded = False + + +def _load_models(): + """Import model modules the first time the registry is queried.""" + global _models_loaded + if _models_loaded: + return + root = 'openquake.hazardlib.correlation_models' + for model_type in ('spatial', 'cross_imt', 'spatial_cross_imt'): + import_all(f'{root}.{model_type}') + _models_loaded = True + + +def _model_type(cls): + if issubclass(cls, SpatialCorrelationModel): + return 'spatial' + if issubclass(cls, CrossIMTCorrelationModel): + return 'cross_imt' + if issubclass(cls, SpatialCrossIMTCorrelationModel): + return 'spatial_cross_imt' + raise TypeError(f'{cls.__name__} is not a correlation model') + + +def register_model(*aliases, description=''): + """Register a model class under its canonical name and aliases.""" + def decorator(cls): + if not issubclass(cls, CorrelationModel): + raise TypeError(f'{cls.__name__} is not a correlation model') + name = cls.name or cls.__name__ + keys = (name,) + tuple(aliases) + duplicates = sorted(key for key in keys if key in registry) + if duplicates: + raise KeyError( + f'Correlation model names already registered: {duplicates}') + spec = ModelSpec( + name, cls, tuple(aliases), _model_type(cls), description) + _specs[name] = spec + for key in keys: + registry[key] = cls + return cls + return decorator + + +def get_model_class(name, model_type=None): + """Return the class registered under ``name``.""" + _load_models() + try: + cls = registry[name] + except KeyError as exc: + available = ', '.join(sorted(registry)) + raise KeyError( + f'Unknown correlation model {name!r}; available: {available}' + ) from exc + if model_type is not None and _model_type(cls) != model_type: + raise TypeError( + f'{name} is {_model_type(cls)}, not {model_type}') + return cls + + +def get_model(name, model_type=None, **parameters): + """Instantiate and validate the model registered under ``name``.""" + model = get_model_class(name, model_type)(**parameters) + model.validate() + return model + + +def get_model_specs(model_type=None): + """Return canonical model specifications, optionally by model type.""" + _load_models() + if model_type is None: + return dict(_specs) + return {name: spec for name, spec in _specs.items() + if spec.model_type == model_type} diff --git a/openquake/hazardlib/correlation_models/spatial/__init__.py b/openquake/hazardlib/correlation_models/spatial/__init__.py new file mode 100644 index 000000000000..702fcc5a94b7 --- /dev/null +++ b/openquake/hazardlib/correlation_models/spatial/__init__.py @@ -0,0 +1 @@ +"""Same-IMT spatial correlation models.""" diff --git a/openquake/hazardlib/correlation_models/spatial/heresi_miranda_2019.py b/openquake/hazardlib/correlation_models/spatial/heresi_miranda_2019.py new file mode 100644 index 000000000000..a478d17c13b0 --- /dev/null +++ b/openquake/hazardlib/correlation_models/spatial/heresi_miranda_2019.py @@ -0,0 +1,92 @@ +# The Hazard Library +# Copyright (C) 2012-2026 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 . +"""Heresi and Miranda (2019) spatial correlation model. + +References +---------- +Heresi Venegas, P. C., and Miranda Mijares, E. (2019). Uncertainty in +intraevent spatial correlation of elastic pseudo-acceleration spectral +ordinates. Bulletin of Earthquake Engineering, 17(3), 1099-1115. +https://doi.org/10.1007/s10518-018-0506-6 +""" + +import numpy + +from openquake.hazardlib.correlation_models.base import ( + ResidualComponent, SpatialCorrelationModel) +from openquake.hazardlib.correlation_models.registry import register_model + + +def _correlation_matrix(sites_or_distances, imt, + uncertainty_multiplier=0): + """Return the Heresi and Miranda (2019) correlation matrix.""" + if hasattr(sites_or_distances, 'mesh'): + distances = sites_or_distances.mesh.get_distance_matrix() + else: + distances = sites_or_distances + + period = imt.period + if period < 1.37: + median_beta = 4.231 * period ** 2 - 5.180 * period + 13.392 + else: + median_beta = 0.140 * period ** 2 - 2.249 * period + 17.050 + stddev_beta = 4.63E-3 * period ** 2 + 0.028 * period + 0.713 + if uncertainty_multiplier: + beta = numpy.random.lognormal( + numpy.log(median_beta), stddev_beta * uncertainty_multiplier) + else: + beta = median_beta + return numpy.exp(-numpy.power(distances / beta, 0.55)) + + +@register_model( + 'HM2019', 'HM2018', 'HM2018CorrelationModel', + description='Heresi and Miranda (2019) spatial correlation') +class HeresiMiranda2019(SpatialCorrelationModel): + """Within-event spatial correlation by Heresi and Miranda (2019).""" + + name = 'HeresiMiranda2019' + calibrated_component = ResidualComponent.WITHIN_EVENT + supported_imts = ('PGA', 'SA') + + def __init__(self, uncertainty_multiplier=0): + super().__init__() + self.uncertainty_multiplier = uncertainty_multiplier + self.distance_matrix = {} + + def correlation_matrix(self, sites, imt, component=None, context=None): + self._get_component(component) + return _correlation_matrix( + sites, imt, self.uncertainty_multiplier) + + def apply_correlation(self, sites, imt, residuals, stddev_intra): + num_sites = len(sites) + assert len(residuals) == len(stddev_intra) == num_sites + stddev_matrix = numpy.diag(stddev_intra) + if self.uncertainty_multiplier == 0: + normalized = residuals / stddev_intra[:, None] + covariance = (stddev_matrix @ + self.correlation_matrix(sites, imt) @ + stddev_matrix) + return numpy.linalg.cholesky(covariance) @ normalized + + correlated = numpy.zeros_like(residuals) + for sample in range(residuals.shape[1]): + correlation = self.correlation_matrix(sites, imt) + covariance = stddev_matrix @ correlation @ stddev_matrix + correlated[:, sample] = numpy.random.multivariate_normal( + numpy.zeros(num_sites), covariance, 1) + return correlated diff --git a/openquake/hazardlib/correlation_models/spatial/jayaram_baker_2009.py b/openquake/hazardlib/correlation_models/spatial/jayaram_baker_2009.py new file mode 100644 index 000000000000..d79e08aec5fe --- /dev/null +++ b/openquake/hazardlib/correlation_models/spatial/jayaram_baker_2009.py @@ -0,0 +1,68 @@ +# The Hazard Library +# Copyright (C) 2012-2026 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 . +"""Jayaram and Baker (2009) spatial correlation model. + +References +---------- +Jayaram, N., and Baker, J. W. (2009). Correlation model for spatially +distributed ground-motion intensities. Earthquake Engineering & Structural +Dynamics, 38(15), 1687-1708. https://doi.org/10.1002/eqe.922 +""" + +import numpy + +from openquake.hazardlib.correlation_models.base import ( + ResidualComponent, SpatialCorrelationModel) +from openquake.hazardlib.correlation_models.registry import register_model + + +def _correlation_matrix(sites_or_distances, imt, + vs30_clustering=False): + """Return the Jayaram and Baker (2009) correlation matrix.""" + if hasattr(sites_or_distances, 'mesh'): + distances = sites_or_distances.mesh.get_distance_matrix() + else: + distances = sites_or_distances + + period = 1.0 if imt.string == 'PGV' else imt.period + if period < 1: + if vs30_clustering: + decay_range = 40.7 - 15.0 * period + else: + decay_range = 8.5 + 17.2 * period + else: + decay_range = 22.0 + 3.7 * period + return numpy.exp((-3.0 / decay_range) * distances) + + +@register_model( + 'JB2009', 'JB2009CorrelationModel', + description='Jayaram and Baker (2009) spatial correlation') +class JayaramBaker2009(SpatialCorrelationModel): + """Within-event spatial correlation by Jayaram and Baker (2009).""" + + name = 'JayaramBaker2009' + calibrated_component = ResidualComponent.WITHIN_EVENT + supported_imts = ('PGA', 'PGV', 'SA') + + def __init__(self, vs30_clustering): + super().__init__() + self.vs30_clustering = vs30_clustering + + def correlation_matrix(self, sites, imt, component=None, context=None): + self._get_component(component) + return _correlation_matrix( + sites, imt, self.vs30_clustering) diff --git a/openquake/hazardlib/correlation_models/spatial_cross_imt/__init__.py b/openquake/hazardlib/correlation_models/spatial_cross_imt/__init__.py new file mode 100644 index 000000000000..c9f2aed53d71 --- /dev/null +++ b/openquake/hazardlib/correlation_models/spatial_cross_imt/__init__.py @@ -0,0 +1 @@ +"""Joint spatial and cross-IMT correlation models.""" diff --git a/openquake/hazardlib/cross_correlation.py b/openquake/hazardlib/cross_correlation.py index 949817811719..0ee16c3d5cd3 100644 --- a/openquake/hazardlib/cross_correlation.py +++ b/openquake/hazardlib/cross_correlation.py @@ -1,322 +1,46 @@ -# -*- coding: utf-8 -*- -# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# The Hazard Library +# Copyright (C) 2021-2026 GEM Foundation # -# Copyright (C) 2021, 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 +# 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. # -# OpenQuake is distributed in the hope that it will be useful, +# 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 +# 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 . - -import numpy as np -from scipy import constants, stats -from abc import ABC, abstractmethod -from openquake.hazardlib.imt import IMT -from openquake.hazardlib.truncated_mvn import TruncatedMVN -from openquake.hazardlib.correlation_utils import corr_clipped - -# ############ CrossCorrelation for the conditional spectrum ############ # - -class CrossCorrelation(ABC): - # TODO We need to specify the HORIZONTAL GMM COMPONENT used - @abstractmethod - def get_correlation(self, from_imt: IMT, to_imt: IMT) -> float: - """ - :param from_imt: - An intensity measure type - :param to_imt: - An intensity measure type - :return: a scalar - """ - - def get_cross_correlation_mtx(self, imts: list) -> np.ndarray: - """ - :param imts: - A list of :class:`openquake.hazardlib.imt.IMT` instances - :returns: - A :class:`numpy.ndarray` instance with shape (|imts|, |imts|) - containing the correlation coefficients between the IMTs provided - in the input `imts` list. - """ - num_imts = len(imts) - mtx = np.zeros((num_imts, num_imts), np.float32) - for i1 in range(num_imts): - for i2 in range(i1, num_imts): - cor = self.get_correlation(imts[i1], imts[i2]) - mtx[i1, i2] = cor - mtx[i2, i1] = cor - return mtx - - -class BakerJayaram2008(CrossCorrelation): - """ - Implements the correlation model of Baker and Jayaram published in 2008 - on Earthquake Spectra. This model works for GMRotI50. - """ - def get_correlation(self, from_imt: IMT, to_imt: IMT) -> float: - - from_per = from_imt.period - to_per = to_imt.period - - if np.abs(from_per-to_per) < 1e-10: - return 1.0 - - t_min = np.min([from_per, to_per]) - t_max = np.max([from_per, to_per]) - - c1 = 1 - np.cos(constants.pi/2 - - 0.366 * np.log(t_max/np.max([t_min, 0.109]))) - c2 = 0.0 - if t_max < 0.2: - term1 = 1.0 - 1.0/(1.0+np.exp(100.0*t_max-5.0)) - term2 = (t_max-t_min) / (t_max-0.0099) - c2 = 1 - 0.105 * term1 * term2 - c3 = c1 - if t_max < 0.109: - c3 = c2 - c4 = c1 + 0.5 * (np.sqrt(c3) - c3) * ( - 1 + np.cos(constants.pi*t_min/0.109)) - if t_max < 0.109: - corr = c2 - elif t_min > 0.109: - corr = c1 - elif t_max < 0.2: - corr = np.amin([c2, c4]) - else: - corr = c4 - return corr # a scalar - - -# ######################## CrossCorrelationBetween ########################## # - -class CrossCorrelationBetween(ABC): - def __init__(self, truncation_level=99.): - if truncation_level < 1E-9: - truncation_level = 1E-9 - self.truncation_level = truncation_level - self.distribution = stats.truncnorm(-truncation_level, truncation_level) - - @abstractmethod - def get_correlation(self, from_imt: IMT, to_imt: IMT) -> float: - """ - :param from_imt: - An intensity measure type - :param to_imt: - An intensity measure type - :return: a scalar - """ - - @abstractmethod - def get_inter_eps(self, imts, num_events, rng): - pass - - def _get_inter_eps_trunc_mvn(self, corma, num_events, rng): - num_imts = len(corma) - mu = np.zeros(num_imts) - bounds = np.full(num_imts, self.truncation_level) - seed = int(rng.integers(0, np.iinfo(np.int32).max)) - # TruncatedMVN mutates the cov matrix in-place during factorization: - # always work on a copy to avoid corrupting cached correlation matrices - corr = np.array(corma, copy=True) - mineig = np.linalg.eigvalsh(corr).min() - if not np.isfinite(mineig) or mineig < 1e-8: - # TruncatedMVN is numerically unstable for nearly-singular matrices. - corr = corr_clipped(corr, threshold=1e-8) - try: - samp = TruncatedMVN(mu, corr, -bounds, bounds, seed=seed).sample( - num_events) - if np.isfinite(samp).all(): - return samp - except RuntimeError as err: - if 'not positive semi-definite' not in str(err): - raise - # Use the existing PSD regularization available in correlation_utils.py - # also in case TruncatedMVN returns non-finite samples. - corr = corr_clipped(corr, threshold=1e-6) - return TruncatedMVN(mu, corr, -bounds, bounds, seed=seed).sample( - num_events) - - -class GodaAtkinson2009(CrossCorrelationBetween): - """ - Implements the correlation model of Goda and Atkinson published in 2009. - This is a correlation model for between-event residuals. See - https://doi.org/10.1785/0120090007 - """ - cache = {} # periods -> correlation matrix - - def get_correlation(self, from_imt: IMT, to_imt: IMT) -> float: - """ - :returns: a scalar in the range 0..1 - """ - if from_imt == to_imt: - return 1.0 - - T1 = from_imt.period or 0.05 # for PGA - T2 = to_imt.period or 0.05 # for PGA - - Tmin = min(T1, T2) - Tmax = max(T1, T2) - ITmin = 1.0 if Tmin < 0.25 else 0.0 - - theta1 = 1.374 - theta2 = 5.586 - theta3 = 0.728 - - angle = np.pi/2.0 - (theta1 + theta2 * ITmin * (Tmin / Tmax) ** theta3 * - np.log10(Tmin / 0.25)) * np.log10(Tmax / Tmin) - delta = 1.0 + np.cos(-1.5 * np.log10(Tmax / Tmin)) - corr = (1.0 - np.cos(angle) + delta) / 3.0 - return min(corr, 1.0) - - def get_inter_eps(self, imts, num_events, rng): - """ - :param imts: a list of M intensity measure types - :param num_events: the number of events to consider (E) - :param rng: random number generator - :returns: a correlated matrix of epsilons of shape (M, E) - - NB: the user must specify the random seed first - """ - corma = self._get_correlation_matrix(imts) - return self._get_inter_eps_trunc_mvn(corma, num_events, rng) - - def _get_correlation_matrix(self, imts): - # cached on the periods - periods = tuple(imt.period for imt in imts) - try: - return self.cache[periods] - except KeyError: - self.cache[periods] = corma = np.zeros( - (len(imts), len(imts)), np.float32) - for i, imi in enumerate(imts): - for j, imj in enumerate(imts): - corma[i, j] = self.get_correlation(imi, imj) - return corma - - -class Bradley2012(CrossCorrelationBetween): - """ - Implements the correlation model for total residuals - between Peak Ground Velocity and Spectrum-Based - Intensity Measures from Bradley, B. A. (2012). - 'Empirical correlations between peak ground velocity - and spectrum-based intensity measures.' - Earthquake Spectra, 28(1), 17–35. - https://doi.org/10.1193/1.3675582 - """ - cache = {} # periods -> correlation matrix - - def get_correlation(self, from_imt: IMT, to_imt: IMT) -> float: - """ - :returns: a scalar in the range 0..1 - """ - - if from_imt == to_imt: - return 1 - if from_imt.string != 'PGV' and to_imt.string != 'PGV': - return 0 - - if from_imt.string == 'PGV': - T = to_imt.period - else: - T = from_imt.period - - if T < 0.01: - return 0.733 - elif T < 0.1: - a = 0.73 - b = 0.54 - c = 0.045 - d = 1.8 - elif T < 0.75: - a = 0.54 - b = 0.81 - c = 0.28 - d = 1.5 - elif T < 2.5: - a = 0.80 - b = 0.76 - c = 1.1 - d = 3.0 - else: - a = 0.76 - b = 0.70 - c = 5.0 - d = 3.2 - - return ((a + b) / 2 - (a - b) / 2 * np.tanh(d * np.log(T / c))) - - def get_inter_eps(self, imts, num_events, rng): - """ - :param imts: a list of M intensity measure types - :param num_events: the number of events to consider (E) - :param rng: random number generator - :returns: a correlated matrix of epsilons of shape (M, E) - - NB: the user must specify the random seed first - """ - corma = self._get_correlation_matrix(imts) - return self._get_inter_eps_trunc_mvn(corma, num_events, rng) - - def _get_correlation_matrix(self, imts): - # cached on the periods - periods = tuple(imt.period for imt in imts) - try: - return self.cache[periods] - except KeyError: - self.cache[periods] = corma = np.zeros((len(imts), len(imts))) - for i, imi in enumerate(imts): - for j, imj in enumerate(imts): - corma[i, j] = self.get_correlation(imi, imj) - return corma - - -class NoCrossCorrelation(CrossCorrelationBetween): - """ - Used when there is no cross correlation - """ - def get_correlation(self, from_imt, to_imt): - return from_imt == to_imt - - def get_inter_eps(self, imts, num_events, rng): - """ - :param imts: a list of M intensity measure types - :param num_events: the number of events to consider (E) - :param rng: random number generator - :returns: an uncorrelated matrix of epsilons of shape (M, E) - - NB: the user must specify the random seed first - """ - return np.array([ - self.distribution.rvs(num_events, rng) for imt in imts]) - - -class FullCrossCorrelation(CrossCorrelationBetween): - """ - Used when there is full cross correlation, i.e. same epsilons for all IMTs - """ - def get_correlation(self, from_imt, to_imt): - return 1. - - def get_inter_eps(self, imts, num_events, rng): - """ - :param imts: a list of M intensity measure types - :param num_events: the number of events to consider (E) - :param rng: random number generator - :returns: - a matrix of epsilons of shape (M, E) with the same epsilons - for each IMT - - NB: the user must specify the random seed first - """ - eps = self.distribution.rvs(num_events, rng) - return np.array([eps for imt in imts]) +# along with this program. If not, see . +"""Compatibility imports for cross-IMT ground-motion correlation.""" + +# DEPRECATION +# ----------- +# New code should import cross-IMT models from +# ``openquake.hazardlib.correlation_models.cross_imt``. This module remains +# available during the correlation-framework migration so existing imports, +# job files, and downstream libraries keep working without changed results. +# Remove only after the documented deprecation window and migration of all +# internal callers. + +from openquake.hazardlib.correlation_models.base import ( + CrossIMTCorrelationModel as CrossCorrelation, + TruncatedCrossIMTCorrelationModel as CrossCorrelationBetween) +from openquake.hazardlib.correlation_models.cross_imt.baker_cornell_2006 import ( + BakerCornell2006) +from openquake.hazardlib.correlation_models.cross_imt.baker_jayaram_2008 import ( + BakerJayaram2008) +from openquake.hazardlib.correlation_models.cross_imt.bradley_2012 import ( + Bradley2012) +from openquake.hazardlib.correlation_models.cross_imt.full_cross_correlation import ( + FullCrossCorrelation) +from openquake.hazardlib.correlation_models.cross_imt.goda_atkinson_2009 import ( + GodaAtkinson2009) +from openquake.hazardlib.correlation_models.cross_imt.no_cross_correlation import ( + NoCrossCorrelation) + +__all__ = [ + 'BakerCornell2006', 'BakerJayaram2008', 'Bradley2012', 'CrossCorrelation', + 'CrossCorrelationBetween', 'FullCrossCorrelation', + 'GodaAtkinson2009', 'NoCrossCorrelation'] diff --git a/openquake/hazardlib/shakemap/gmfs.py b/openquake/hazardlib/shakemap/gmfs.py index cfc2567f4671..ed3bcadaaeed 100644 --- a/openquake/hazardlib/shakemap/gmfs.py +++ b/openquake/hazardlib/shakemap/gmfs.py @@ -16,14 +16,13 @@ # You should have received a copy of the GNU Affero General Public License # along with OpenQuake. If not, see . -import math import logging import numpy from scipy.stats import truncnorm from scipy import interpolate from openquake.baselib.general import CallableDict -from openquake.hazardlib import geo, imt, correlation +from openquake.hazardlib import geo, imt F32 = numpy.float32 PCTG = 100 # percent of g, the gravity acceleration @@ -31,25 +30,21 @@ MAX_PGV_GMV = 500. # cm/s -def spatial_correlation_array(dmatrix, imts, correl='yes', - vs30clustered=True): +def spatial_correlation_array(dmatrix, imts, model): """ :param dmatrix: distance matrix of shape (N, N) :param imts: M intensity measure types - :param correl: 'yes', 'no' or 'full' - :param vs30clustered: flag, True by default + :param model: spatial correlation model :returns: array of shape (M, N, N) """ - assert correl in 'yes no full', correl n = len(dmatrix) + if model is None: + return numpy.repeat( + numpy.eye(n)[numpy.newaxis], len(imts), axis=0) + model.validate_imts(imts) corr = numpy.zeros((len(imts), n, n)) - for imti, im in enumerate(imts): - if correl == 'no': - corr[imti] = numpy.eye(n) - if correl == 'full': - corr[imti] = numpy.ones((n, n)) - elif correl == 'yes': - corr[imti] = correlation.jbcorrelation(dmatrix, im, vs30clustered) + for imti, imt_ in enumerate(imts): + corr[imti] = model.correlation_matrix(dmatrix, imt_) return corr @@ -69,34 +64,16 @@ def spatial_covariance_array(stddev, corrmatrices): return numpy.array(matrices) -def cross_correlation_matrix(imts, corr='yes'): +def cross_correlation_matrix(imts, model): """ :param imts: M intensity measure types - :param corr: 'yes', 'no' or 'full' + :param model: cross-IMT correlation model :returns: an array of shape (M, M) """ - assert corr in 'yes no full', corr - # if there is only PGA this is a 1x1 identity matrix - M = len(imts) - cross_matrix = numpy.eye(M) - if corr == 'full': - cross_matrix = numpy.full((M, M), 0.99999) - numpy.fill_diagonal(cross_matrix, 1) - elif corr == 'yes': - for i, im in enumerate(imts): - T1 = im.period or 0.05 - - for j in range(M): - if i == j: - continue - T2 = imts[j].period or 0.05 - Tmax = max(T1, T2) - Tmin = min(T1, T2) - II = 1 if Tmin < 0.189 else 0 - cross_matrix[i, j] = 1 - math.cos(math.pi / 2 - ( - 0.359 + 0.163 * II * math.log(Tmin / 0.189) - ) * math.log(Tmax / Tmin)) - return cross_matrix + if model is None: + return numpy.eye(len(imts)) + model.validate_imts(imts) + return model.correlation_matrix(imts) # not used since the ShakeMap does it already @@ -188,16 +165,16 @@ def _build_imt_scaling_vector(imts, shakemap_std, pctg_value): @calculate_gmfs.add('Silva&Horspool') -def calculate_gmfs_sh(kind, shakemap, imts, Z, mu, spatialcorr, - crosscorr, cholesky_limit): +def calculate_gmfs_sh(kind, shakemap, imts, Z, mu, spatial_model, + cross_imt_model, cholesky_limit): """ Implementation of paper by Silva and Horspool 2019 https://onlinelibrary.wiley.com/doi/abs/10.1002/eqe.3154?af=R :param shakemap: site coordinates with median ShakeMap values :param imts: list of required imts - :param spatialcorr: 'no', 'yes' or 'full' - :param crosscorr: 'no', 'yes' or 'full' + :param spatial_model: spatial correlation model + :param cross_imt_model: cross-IMT correlation model :returns: F(Z, mu) to calculate gmfs """ # make sure all imts used have a period, needed for correlation @@ -210,11 +187,12 @@ def calculate_gmfs_sh(kind, shakemap, imts, Z, mu, spatialcorr, f'{M=} x {N=}', cholesky_limit)) # Cross Correlation - cross_corr = cross_correlation_matrix(imts, crosscorr) + cross_corr = cross_correlation_matrix(imts, cross_imt_model) # Spatial Correlation and Covariance dmatrix = geo.geodetic.distance_matrix(shakemap['lon'], shakemap['lat']) - spatial_corr = spatial_correlation_array(dmatrix, imts, spatialcorr) + spatial_corr = spatial_correlation_array( + dmatrix, imts, spatial_model) eig, _ = numpy.linalg.eigh(spatial_corr[0]) small_eig = (eig < 1E-6).sum() diff --git a/openquake/hazardlib/tests/calc/cond_spectra_test.py b/openquake/hazardlib/tests/calc/cond_spectra_test.py index b63f82641639..f122fadb8d77 100644 --- a/openquake/hazardlib/tests/calc/cond_spectra_test.py +++ b/openquake/hazardlib/tests/calc/cond_spectra_test.py @@ -42,7 +42,7 @@ width_of_mfd_bin=1., investigation_time=1, truncation_level=3, - cross_correl=BakerJayaram2008(), + cross_imt_model=BakerJayaram2008(), imtls={"SA(0.05)": valid.logscale(0.005, 2.13, 45), "SA(0.075)": valid.logscale(0.005, 2.13, 45), "SA(0.1)": valid.logscale(0.005, 2.13, 45), diff --git a/openquake/hazardlib/tests/calc/data/job.ini b/openquake/hazardlib/tests/calc/data/job.ini index 75733faafadb..ba8319b6a370 100644 --- a/openquake/hazardlib/tests/calc/data/job.ini +++ b/openquake/hazardlib/tests/calc/data/job.ini @@ -48,6 +48,6 @@ truncation_level = 3 maximum_distance = 200.0 # conditional_spectrum parameters -cross_correlation = BakerJayaram2008 +cross_imt_correlation_model = BakerJayaram2008 imt_ref = SA(0.2) poes = 0.002105 diff --git a/openquake/hazardlib/tests/correlation_models_test.py b/openquake/hazardlib/tests/correlation_models_test.py new file mode 100644 index 000000000000..87e3ec32ec70 --- /dev/null +++ b/openquake/hazardlib/tests/correlation_models_test.py @@ -0,0 +1,126 @@ +# The Hazard Library +# Copyright (C) 2026 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 . + +import numpy +import pytest + +from openquake.hazardlib import correlation, cross_correlation +from openquake.hazardlib import correlation_models +from openquake.hazardlib.correlation_models.base import ( + ResidualComponent, SpatialCrossIMTCorrelationModel) +from openquake.hazardlib.correlation_models.cross_imt.baker_cornell_2006 import ( + BakerCornell2006) +from openquake.hazardlib.correlation_models.cross_imt.baker_jayaram_2008 import ( + BakerJayaram2008) +from openquake.hazardlib.correlation_models.cross_imt.bradley_2012 import ( + Bradley2012) +from openquake.hazardlib.correlation_models.cross_imt.goda_atkinson_2009 import ( + GodaAtkinson2009) +from openquake.hazardlib.correlation_models.registry import ( + get_model, get_model_class, get_model_specs) +from openquake.hazardlib.correlation_models.spatial.heresi_miranda_2019 import ( + HeresiMiranda2019) +from openquake.hazardlib.correlation_models.spatial.jayaram_baker_2009 import ( + JayaramBaker2009) +from openquake.hazardlib.imt import PGA, SA + + +def test_registry_aliases_and_metadata(): + assert get_model_class('HM2018') is HeresiMiranda2019 + assert get_model_class('HM2019') is HeresiMiranda2019 + assert get_model_class('HeresiMiranda2019') is HeresiMiranda2019 + assert get_model_class('JB2009') is JayaramBaker2009 + assert get_model_class('JayaramBaker2009') is JayaramBaker2009 + assert get_model_class('GodaAtkinson2009') is GodaAtkinson2009 + assert get_model_class('Bradley2012') is Bradley2012 + assert get_model_class('BakerCornell2006') is BakerCornell2006 + specs = get_model_specs('spatial') + assert specs['HeresiMiranda2019'].aliases == ( + 'HM2019', 'HM2018', 'HM2018CorrelationModel') + assert specs['JayaramBaker2009'].aliases == ( + 'JB2009', 'JB2009CorrelationModel') + assert specs['JayaramBaker2009'].calibrated_component == ( + ResidualComponent.WITHIN_EVENT) + assert specs['JayaramBaker2009'].supported_imts == ( + 'PGA', 'PGV', 'SA') + cross_imt = get_model_specs('cross_imt') + assert cross_imt['GodaAtkinson2009'].calibrated_component == ( + ResidualComponent.BETWEEN_EVENT) + assert cross_imt['Bradley2012'].calibrated_component == ( + ResidualComponent.TOTAL) + + +def test_registry_instantiation_and_type_validation(): + model = get_model( + 'JB2009', 'spatial', vs30_clustering=False) + assert isinstance(model, JayaramBaker2009) + with pytest.raises(TypeError, match='not spatial'): + get_model_class('BakerJayaram2008', model_type='spatial') + with pytest.raises(KeyError, match='Unknown correlation model'): + get_model_class('MissingModel') + + +def test_package_does_not_reexport_models(): + assert not hasattr(correlation_models, 'JayaramBaker2009') + assert not hasattr(correlation_models, 'get_model') + + +def test_residual_component_validation(): + model = BakerJayaram2008() + assert model.rho( + SA(0.1), SA(0.5), ResidualComponent.TOTAL + ) == pytest.approx(0.4745240873) + with pytest.raises(ValueError, match='provides total correlation'): + model.rho(SA(0.1), SA(0.5), + ResidualComponent.WITHIN_EVENT) + + +def test_cross_im_covariance_uses_imt_major_ordering(): + model = BakerJayaram2008() + imts = [PGA(), SA(0.5)] + sites = range(2) + correlation_value = model.rho(*imts) + expected = numpy.array([ + [1, 0, correlation_value, 0], + [0, 1, 0, correlation_value], + [correlation_value, 0, 1, 0], + [0, correlation_value, 0, 1], + ]) + numpy.testing.assert_allclose( + model.covariance(sites, imts), expected) + + +def test_default_factor_repairs_indefinite_covariance(): + class IndefiniteModel(SpatialCrossIMTCorrelationModel): + def covariance(self, sites, imts, component=None, context=None): + return numpy.array([[1.0, 1.01], [1.01, 1.0]]) + + model = IndefiniteModel() + with pytest.raises(numpy.linalg.LinAlgError): + model.factor(None, None, ensure_psd=False) + factor = model.factor(None, None) + repaired = factor.lower_triangle @ factor.lower_triangle.T + assert numpy.linalg.eigvalsh(repaired).min() > 0 + + +def test_legacy_modules_export_canonical_classes(): + assert correlation.JB2009CorrelationModel is JayaramBaker2009 + assert correlation.HM2018CorrelationModel is HeresiMiranda2019 + assert cross_correlation.BakerJayaram2008 is BakerJayaram2008 + assert cross_correlation.GodaAtkinson2009 is GodaAtkinson2009 + assert issubclass( + cross_correlation.NoCrossCorrelation, + cross_correlation.CrossCorrelationBetween) diff --git a/openquake/hazardlib/tests/shakemap/shakemap_test.py b/openquake/hazardlib/tests/shakemap/shakemap_test.py index de7143357279..b3dfe84c40e2 100644 --- a/openquake/hazardlib/tests/shakemap/shakemap_test.py +++ b/openquake/hazardlib/tests/shakemap/shakemap_test.py @@ -2,6 +2,10 @@ import unittest import numpy from openquake.hazardlib import geo, imt +from openquake.hazardlib.correlation_models.cross_imt.baker_cornell_2006 import ( + BakerCornell2006) +from openquake.hazardlib.correlation_models.spatial.jayaram_baker_2009 import ( + JayaramBaker2009) from openquake.hazardlib.shakemap.maps import \ get_sitecol_shakemap from openquake.hazardlib.shakemap.gmfs import ( @@ -17,9 +21,11 @@ ('std', imt_dt), ('vs30', float)]) CDIR = os.path.dirname(__file__) +spatial_model = JayaramBaker2009(vs30_clustering=True) +cross_imt_model = BakerCornell2006() gmf_dict = {'kind': 'Silva&Horspool', - 'spatialcorr': 'yes', - 'crosscorr': 'yes', + 'spatial_model': spatial_model, + 'cross_imt_model': cross_imt_model, 'cholesky_limit': 10000} @@ -29,7 +35,8 @@ def aae(x, y): def mean_std(shakemap, vs30): gmf_dict.update({'kind': 'Silva&Horspool', - 'spatialcorr': 'yes', 'crosscorr': 'yes'}) + 'spatial_model': spatial_model, + 'cross_imt_model': cross_imt_model}) _, gmfs = to_gmfs( shakemap, gmf_dict, vs30, truncation_level=3, num_gmfs=1000, seed=42, imts=['PGA', 'SA(0.3)', 'SA(1.0)', 'SA(3.0)']) @@ -68,7 +75,7 @@ def test_matrices(self): aae(dmatrix.sum(), 18539.605) # spatial correlation - sca = spatial_correlation_array(dmatrix, imts, 'yes') + sca = spatial_correlation_array(dmatrix, imts, spatial_model) aae(sca.sum(), 36.000370229) # spatial covariance @@ -77,7 +84,7 @@ def test_matrices(self): aae(scov.sum(), 13.166200147) # cross correlation - ccor = cross_correlation_matrix(imts, 'yes') + ccor = cross_correlation_matrix(imts, cross_imt_model) aae(ccor.sum(), 10.49124788) # cholesky decomposition @@ -96,9 +103,18 @@ def test_matrices(self): shakemap['val'] = val shakemap['std'] = std gmf_dict.update({'kind': 'Silva&Horspool', - 'spatialcorr': 'yes', 'crosscorr': 'no'}) + 'spatial_model': spatial_model, + 'cross_imt_model': None}) self._check(shakemap, gmf_dict) + def test_identity_matrices_without_models(self): + distances = numpy.zeros((2, 2)) + numpy.testing.assert_array_equal( + spatial_correlation_array(distances, imts, None), + numpy.repeat(numpy.eye(2)[None], len(imts), axis=0)) + numpy.testing.assert_array_equal( + cross_correlation_matrix(imts, None), numpy.eye(len(imts))) + def test_no_uncertainty_preserves_shakemap(self): value_dt = numpy.dtype([('PGA', float), ('PGV', float)]) dtype = numpy.dtype([ @@ -121,8 +137,8 @@ def test_no_uncertainty_preserves_shakemap(self): ]) methods = [ {'kind': 'basic'}, - {'kind': 'Silva&Horspool', 'spatialcorr': 'no', - 'crosscorr': 'no', 'cholesky_limit': 10000}, + {'kind': 'Silva&Horspool', 'spatial_model': None, + 'cross_imt_model': None, 'cholesky_limit': 10000}, ] for method in methods: for truncation_level in (0, 1E-9): @@ -140,7 +156,8 @@ def _check(self, shakemap, gmf_dict): aae(gmfs[..., 0].sum(axis=0), [0.4202056, 0.6426098]) # PGA gmf_dict.update({'kind': 'Silva&Horspool', - 'spatialcorr': 'yes', 'crosscorr': 'yes'}) + 'spatial_model': spatial_model, + 'cross_imt_model': cross_imt_model}) _, gmfs = to_gmfs( shakemap, gmf_dict, vs30=shakemap['vs30'], truncation_level=3, num_gmfs=2, seed=42) @@ -149,7 +166,8 @@ def _check(self, shakemap, gmf_dict): # disable spatial correlation gmf_dict.update({'kind': 'Silva&Horspool', - 'spatialcorr': 'no', 'crosscorr': 'no'}) + 'spatial_model': None, + 'cross_imt_model': None}) _, gmfs = to_gmfs( shakemap, gmf_dict, vs30=None, truncation_level=3, num_gmfs=2, seed=42) @@ -163,7 +181,8 @@ def _check(self, shakemap, gmf_dict): aae(gmfs[..., 0].sum(axis=0), [0.4202077, 0.6426078]) # PGA gmf_dict.update({'kind': 'Silva&Horspool', - 'spatialcorr': 'no', 'crosscorr': 'yes'}) + 'spatial_model': None, + 'cross_imt_model': cross_imt_model}) _, gmfs = to_gmfs( shakemap, gmf_dict, vs30=shakemap['vs30'], truncation_level=3, num_gmfs=2, seed=42) @@ -174,7 +193,8 @@ def _check(self, shakemap, gmf_dict): shakemap['std'] = 0 with self.assertRaises(ValueError) as ctx: gmf_dict.update({'kind': 'Silva&Horspool', - 'spatialcorr': 'no', 'crosscorr': 'yes'}) + 'spatial_model': None, + 'cross_imt_model': cross_imt_model}) to_gmfs(shakemap, gmf_dict, vs30=shakemap['vs30'], truncation_level=3, num_gmfs=2, seed=42) self.assertIn('stddev==0 for IMT=PGA', str(ctx.exception)) diff --git a/openquake/qa_tests_data/conditional_spectrum/case_1/job.ini b/openquake/qa_tests_data/conditional_spectrum/case_1/job.ini index d7b35c434f7a..154526d559a4 100644 --- a/openquake/qa_tests_data/conditional_spectrum/case_1/job.ini +++ b/openquake/qa_tests_data/conditional_spectrum/case_1/job.ini @@ -40,6 +40,6 @@ intensity_measure_types_and_levels = { truncation_level = 3 maximum_distance = 100.0 -cross_correlation = BakerJayaram2008 +cross_imt_correlation_model = BakerJayaram2008 imt_ref = SA(0.2) poes = 0.1 0.02 diff --git a/openquake/qa_tests_data/conditional_spectrum/case_2/job.ini b/openquake/qa_tests_data/conditional_spectrum/case_2/job.ini index 15935dcb979b..2243aeaeb6c0 100644 --- a/openquake/qa_tests_data/conditional_spectrum/case_2/job.ini +++ b/openquake/qa_tests_data/conditional_spectrum/case_2/job.ini @@ -37,6 +37,6 @@ intensity_measure_types_and_levels = { "SA(1.0)": logscale(.001, 3, 20)} truncation_level = 3 maximum_distance = 100.0 -cross_correlation = BakerJayaram2008 +cross_imt_correlation_model = BakerJayaram2008 imt_ref = SA(0.2) poes = 0.002105 0.000404 diff --git a/openquake/qa_tests_data/conditional_spectrum/case_3/job.ini b/openquake/qa_tests_data/conditional_spectrum/case_3/job.ini index 5470b70ad3fd..21aa6c74498f 100644 --- a/openquake/qa_tests_data/conditional_spectrum/case_3/job.ini +++ b/openquake/qa_tests_data/conditional_spectrum/case_3/job.ini @@ -37,6 +37,6 @@ intensity_measure_types_and_levels = { truncation_level = 3 maximum_distance = 100.0 -cross_correlation = BakerJayaram2008 +cross_imt_correlation_model = BakerJayaram2008 imt_ref = SA(0.2) poes = 0.02 diff --git a/openquake/qa_tests_data/event_based/case_1/job.ini b/openquake/qa_tests_data/event_based/case_1/job.ini index a420223c8eff..4ebc3a973350 100644 --- a/openquake/qa_tests_data/event_based/case_1/job.ini +++ b/openquake/qa_tests_data/event_based/case_1/job.ini @@ -43,8 +43,8 @@ minimum_intensity = .1 [event_based_params] ses_per_logic_tree_path = 80000 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_12/job.ini b/openquake/qa_tests_data/event_based/case_12/job.ini index 186aafdfe3d1..000ba0896c35 100644 --- a/openquake/qa_tests_data/event_based/case_12/job.ini +++ b/openquake/qa_tests_data/event_based/case_12/job.ini @@ -44,8 +44,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 3500 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_13/job.ini b/openquake/qa_tests_data/event_based/case_13/job.ini index 4582451e95ef..0a84e5cfcaad 100644 --- a/openquake/qa_tests_data/event_based/case_13/job.ini +++ b/openquake/qa_tests_data/event_based/case_13/job.ini @@ -44,8 +44,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 5000 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_18/job.ini b/openquake/qa_tests_data/event_based/case_18/job.ini index c0dee5bba1b5..d72aed1610b6 100644 --- a/openquake/qa_tests_data/event_based/case_18/job.ini +++ b/openquake/qa_tests_data/event_based/case_18/job.ini @@ -44,8 +44,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 350 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_2/job.ini b/openquake/qa_tests_data/event_based/case_2/job.ini index 03ca2dfe33b1..ec1c6e2fd3d1 100644 --- a/openquake/qa_tests_data/event_based/case_2/job.ini +++ b/openquake/qa_tests_data/event_based/case_2/job.ini @@ -43,8 +43,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 600 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_2/job_2.ini b/openquake/qa_tests_data/event_based/case_2/job_2.ini index 94fd6a33aaea..7decc05b6708 100644 --- a/openquake/qa_tests_data/event_based/case_2/job_2.ini +++ b/openquake/qa_tests_data/event_based/case_2/job_2.ini @@ -43,8 +43,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 600 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_21/job.ini b/openquake/qa_tests_data/event_based/case_21/job.ini index 8905d585a160..b7fbcc8ee1a0 100644 --- a/openquake/qa_tests_data/event_based/case_21/job.ini +++ b/openquake/qa_tests_data/event_based/case_21/job.ini @@ -37,8 +37,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 1 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_22/job.ini b/openquake/qa_tests_data/event_based/case_22/job.ini index 27fae5af3faf..40b1a913cd76 100644 --- a/openquake/qa_tests_data/event_based/case_22/job.ini +++ b/openquake/qa_tests_data/event_based/case_22/job.ini @@ -42,8 +42,8 @@ minimum_intensity = .001 [event_based_params] ses_per_logic_tree_path = 1 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_24/job.ini b/openquake/qa_tests_data/event_based/case_24/job.ini index 3834b639ab80..8c0ebba7c1c1 100644 --- a/openquake/qa_tests_data/event_based/case_24/job.ini +++ b/openquake/qa_tests_data/event_based/case_24/job.ini @@ -8,8 +8,8 @@ ses_seed = 24 [event_based_params] ses_per_logic_tree_path = 1 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [geometry] diff --git a/openquake/qa_tests_data/event_based/case_28/job.ini b/openquake/qa_tests_data/event_based/case_28/job.ini index c4affcfa1166..9318549604b4 100644 --- a/openquake/qa_tests_data/event_based/case_28/job.ini +++ b/openquake/qa_tests_data/event_based/case_28/job.ini @@ -42,8 +42,8 @@ minimum_intensity = .001 [event_based_params] ses_per_logic_tree_path = 1 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} [output] diff --git a/openquake/qa_tests_data/event_based/case_3/job.ini b/openquake/qa_tests_data/event_based/case_3/job.ini index 025376f22969..153972da5462 100644 --- a/openquake/qa_tests_data/event_based/case_3/job.ini +++ b/openquake/qa_tests_data/event_based/case_3/job.ini @@ -43,8 +43,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 5000 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_31/job.ini b/openquake/qa_tests_data/event_based/case_31/job.ini index ee908bf69db7..402fcd8ded85 100644 --- a/openquake/qa_tests_data/event_based/case_31/job.ini +++ b/openquake/qa_tests_data/event_based/case_31/job.ini @@ -34,5 +34,5 @@ minimum_intensity = .01 [event_based_params] ses_per_logic_tree_path = 1 -ground_motion_correlation_model = HM2018 -ground_motion_correlation_params = {'uncertainty_multiplier': 0} +spatial_correlation_model = HeresiMiranda2019 +spatial_correlation_params = {'uncertainty_multiplier': 0} diff --git a/openquake/qa_tests_data/event_based/case_34/job.ini b/openquake/qa_tests_data/event_based/case_34/job.ini index 3583f6ace09b..a78d9c952543 100644 --- a/openquake/qa_tests_data/event_based/case_34/job.ini +++ b/openquake/qa_tests_data/event_based/case_34/job.ini @@ -24,7 +24,7 @@ ses_per_logic_tree_path = 100 truncation_level = 3 intensity_measure_types = PGV, PGA minimum_intensity = {"PGV": .3, "PGA": 0.01} -cross_correlation = Bradley2012 +cross_imt_correlation_model = Bradley2012 investigation_time = 1 maximum_distance = {'default': 400.} minimum_magnitude = 4 diff --git a/openquake/qa_tests_data/event_based/case_4/job.ini b/openquake/qa_tests_data/event_based/case_4/job.ini index 0b91dc9577ee..5de2db16d91e 100644 --- a/openquake/qa_tests_data/event_based/case_4/job.ini +++ b/openquake/qa_tests_data/event_based/case_4/job.ini @@ -43,8 +43,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 50 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_7/job.ini b/openquake/qa_tests_data/event_based/case_7/job.ini index 439d09bce440..8d4ceb4a9721 100644 --- a/openquake/qa_tests_data/event_based/case_7/job.ini +++ b/openquake/qa_tests_data/event_based/case_7/job.ini @@ -40,8 +40,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 10 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/qa_tests_data/event_based/case_9/job.ini b/openquake/qa_tests_data/event_based/case_9/job.ini index 9838ac7dcb92..9ef190a773e9 100644 --- a/openquake/qa_tests_data/event_based/case_9/job.ini +++ b/openquake/qa_tests_data/event_based/case_9/job.ini @@ -23,5 +23,5 @@ ses_per_logic_tree_path = 1 truncation_level = 3 maximum_distance = 50.0 minimum_intensity = .001 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} diff --git a/openquake/qa_tests_data/event_based/spatial_correlation/case_1/job.ini b/openquake/qa_tests_data/event_based/spatial_correlation/case_1/job.ini index 1b87576575aa..2d8ce7f9f198 100644 --- a/openquake/qa_tests_data/event_based/spatial_correlation/case_1/job.ini +++ b/openquake/qa_tests_data/event_based/spatial_correlation/case_1/job.ini @@ -42,5 +42,5 @@ truncation_level = 99. [event_based_params] ses_per_logic_tree_path = 125 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} diff --git a/openquake/qa_tests_data/event_based/spatial_correlation/case_2/job.ini b/openquake/qa_tests_data/event_based/spatial_correlation/case_2/job.ini index 43be4b2df84a..c836d3b7cf7d 100644 --- a/openquake/qa_tests_data/event_based/spatial_correlation/case_2/job.ini +++ b/openquake/qa_tests_data/event_based/spatial_correlation/case_2/job.ini @@ -42,5 +42,5 @@ truncation_level = 99. [event_based_params] ses_per_logic_tree_path = 150 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} diff --git a/openquake/qa_tests_data/event_based_damage/case_11/job.ini b/openquake/qa_tests_data/event_based_damage/case_11/job.ini index 662d6f00b73b..b4a92efada6c 100644 --- a/openquake/qa_tests_data/event_based_damage/case_11/job.ini +++ b/openquake/qa_tests_data/event_based_damage/case_11/job.ini @@ -22,8 +22,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [hazard_calculation] truncation_level = 3 diff --git a/openquake/qa_tests_data/event_based_risk/case_03/job.ini b/openquake/qa_tests_data/event_based_risk/case_03/job.ini index eca7f19c24c9..5d45ba7cf65c 100644 --- a/openquake/qa_tests_data/event_based_risk/case_03/job.ini +++ b/openquake/qa_tests_data/event_based_risk/case_03/job.ini @@ -40,8 +40,8 @@ maximum_distance = 400.0 [event_based_params] ses_per_logic_tree_path = 1 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [risk] diff --git a/openquake/qa_tests_data/event_based_risk/case_1f/job.ini b/openquake/qa_tests_data/event_based_risk/case_1f/job.ini index 3c94bef57a29..a8b14eb92c34 100644 --- a/openquake/qa_tests_data/event_based_risk/case_1f/job.ini +++ b/openquake/qa_tests_data/event_based_risk/case_1f/job.ini @@ -31,8 +31,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/event_based_risk/case_1f/job_h.ini b/openquake/qa_tests_data/event_based_risk/case_1f/job_h.ini index d725c4dd155d..2d78d6d1f9e8 100644 --- a/openquake/qa_tests_data/event_based_risk/case_1f/job_h.ini +++ b/openquake/qa_tests_data/event_based_risk/case_1f/job_h.ini @@ -22,8 +22,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/event_based_risk/case_1g/job_h.ini b/openquake/qa_tests_data/event_based_risk/case_1g/job_h.ini index 236e91cd2687..f751c4afaac9 100644 --- a/openquake/qa_tests_data/event_based_risk/case_1g/job_h.ini +++ b/openquake/qa_tests_data/event_based_risk/case_1g/job_h.ini @@ -26,8 +26,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/event_based_risk/case_6c/job_h.ini b/openquake/qa_tests_data/event_based_risk/case_6c/job_h.ini index a3a40da36d1b..bcc7df341016 100644 --- a/openquake/qa_tests_data/event_based_risk/case_6c/job_h.ini +++ b/openquake/qa_tests_data/event_based_risk/case_6c/job_h.ini @@ -23,8 +23,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [vulnerability] structural_vulnerability_file = vulnerability_model.xml diff --git a/openquake/qa_tests_data/event_based_risk/case_7a/job_h.ini b/openquake/qa_tests_data/event_based_risk/case_7a/job_h.ini index f44c6d1ac4ee..125ccd2a5221 100644 --- a/openquake/qa_tests_data/event_based_risk/case_7a/job_h.ini +++ b/openquake/qa_tests_data/event_based_risk/case_7a/job_h.ini @@ -22,8 +22,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/event_based_risk/case_master/job.ini b/openquake/qa_tests_data/event_based_risk/case_master/job.ini index ca73c3e6b09a..b293b11fa160 100644 --- a/openquake/qa_tests_data/event_based_risk/case_master/job.ini +++ b/openquake/qa_tests_data/event_based_risk/case_master/job.ini @@ -26,8 +26,8 @@ source_model_logic_tree_file = ssmLT.zip gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [hazard_calculation] random_seed = 24 diff --git a/openquake/qa_tests_data/event_based_risk/case_miriam/job2.ini b/openquake/qa_tests_data/event_based_risk/case_miriam/job2.ini index d701ea12008d..0de7472b2ed2 100644 --- a/openquake/qa_tests_data/event_based_risk/case_miriam/job2.ini +++ b/openquake/qa_tests_data/event_based_risk/case_miriam/job2.ini @@ -38,8 +38,8 @@ truncation_level = 99. [event_based_params] ses_per_logic_tree_path = 5 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] intensity_measure_types_and_levels = {'PGA': logscale(0.01, 2.0, 30)} diff --git a/openquake/qa_tests_data/event_based_risk/occupants/job.ini b/openquake/qa_tests_data/event_based_risk/occupants/job.ini index 2c712adf9a3f..bea994f4e8dd 100644 --- a/openquake/qa_tests_data/event_based_risk/occupants/job.ini +++ b/openquake/qa_tests_data/event_based_risk/occupants/job.ini @@ -22,8 +22,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/gmf_ebrisk/case_4/job_haz.ini b/openquake/qa_tests_data/gmf_ebrisk/case_4/job_haz.ini index cd54f9e85fbd..cb009c4835b3 100644 --- a/openquake/qa_tests_data/gmf_ebrisk/case_4/job_haz.ini +++ b/openquake/qa_tests_data/gmf_ebrisk/case_4/job_haz.ini @@ -22,8 +22,8 @@ source_model_logic_tree_file = source_model_logic_tree.xml gsim_logic_tree_file = gsim_logic_tree.xml [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [hazard_calculation] random_seed = 24 diff --git a/openquake/qa_tests_data/infrastructure_risk/case_2/job.ini b/openquake/qa_tests_data/infrastructure_risk/case_2/job.ini index 9ed63cadf145..949bb9f07a3f 100644 --- a/openquake/qa_tests_data/infrastructure_risk/case_2/job.ini +++ b/openquake/qa_tests_data/infrastructure_risk/case_2/job.ini @@ -15,7 +15,7 @@ secondary_perils = Jibson2007BLandslides, AllstadtEtAl2022Liquefaction [Calculation parameters] intensity_measure_types = PGA, SA(0.3), PGV -cross_correlation = Bradley2012 +cross_imt_correlation_model = Bradley2012 truncation_level = 3 maximum_distance = 300 number_of_ground_motion_fields = 10 diff --git a/openquake/qa_tests_data/infrastructure_risk/case_2/job_err.ini b/openquake/qa_tests_data/infrastructure_risk/case_2/job_err.ini index 19092d76633e..d5fe7b02eedb 100644 --- a/openquake/qa_tests_data/infrastructure_risk/case_2/job_err.ini +++ b/openquake/qa_tests_data/infrastructure_risk/case_2/job_err.ini @@ -15,7 +15,7 @@ secondary_perils = Jibson2007BLandslides, AllstadtEtAl2022Liquefaction [Calculation parameters] intensity_measure_types = PGA, SA(0.3), PGV -cross_correlation = Bradley2012 +cross_imt_correlation_model = Bradley2012 truncation_level = 3 maximum_distance = 300 number_of_ground_motion_fields = 10 diff --git a/openquake/qa_tests_data/infrastructure_risk/case_3/job_scenario.ini b/openquake/qa_tests_data/infrastructure_risk/case_3/job_scenario.ini index cfd33a5ff0c3..1bf4e6b6fa53 100644 --- a/openquake/qa_tests_data/infrastructure_risk/case_3/job_scenario.ini +++ b/openquake/qa_tests_data/infrastructure_risk/case_3/job_scenario.ini @@ -19,9 +19,9 @@ secondary_perils = TodorovicSilva2022NonParametric [calculation] intensity_measure_types = PGA, SA(1.0), PGV -cross_correlation = Bradley2012 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +cross_imt_correlation_model = Bradley2012 +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} truncation_level = 3 maximum_distance = 300 number_of_ground_motion_fields = 20 diff --git a/openquake/qa_tests_data/scenario/case_15/job.ini b/openquake/qa_tests_data/scenario/case_15/job.ini index ce76ff360564..9cacd2b9b7ba 100644 --- a/openquake/qa_tests_data/scenario/case_15/job.ini +++ b/openquake/qa_tests_data/scenario/case_15/job.ini @@ -21,8 +21,8 @@ intensity_measure_types = PGA truncation_level = 3.0 maximum_distance = 500 gsim = AtkinsonBoore2006Modified2011 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} number_of_ground_motion_fields = 1 [output] diff --git a/openquake/qa_tests_data/scenario/case_18/job.ini b/openquake/qa_tests_data/scenario/case_18/job.ini index 401c38d606c1..31c861369212 100644 --- a/openquake/qa_tests_data/scenario/case_18/job.ini +++ b/openquake/qa_tests_data/scenario/case_18/job.ini @@ -21,8 +21,8 @@ reference_depth_to_1pt0km_per_sec = 100.0 [Calculation parameters] gsim_logic_tree_file = gmpe_rrup.xml intensity_measure_types = PGA, SA(0.3), SA(0.7), SA(1.0) -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} truncation_level = 3.0 maximum_distance = 300.0 number_of_ground_motion_fields = 10 diff --git a/openquake/qa_tests_data/scenario/case_29/job.ini b/openquake/qa_tests_data/scenario/case_29/job.ini index df9435480246..dd3f6d4fe202 100644 --- a/openquake/qa_tests_data/scenario/case_29/job.ini +++ b/openquake/qa_tests_data/scenario/case_29/job.ini @@ -14,12 +14,12 @@ rupture_mesh_spacing = 2.0 [calculation] intensity_measure_types = PGA, SA(0.3), SA(0.6), SA(1.0) -cross_correlation = GodaAtkinson2009 +cross_imt_correlation_model = GodaAtkinson2009 maximum_distance_stations = 25 random_seed = 42 truncation_level = 1 maximum_distance = 300 gsim = KaleEtAl2015Turkey number_of_ground_motion_fields = 1 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} diff --git a/openquake/qa_tests_data/scenario/case_3/job.ini b/openquake/qa_tests_data/scenario/case_3/job.ini index 82f676302399..39253b5ae48c 100644 --- a/openquake/qa_tests_data/scenario/case_3/job.ini +++ b/openquake/qa_tests_data/scenario/case_3/job.ini @@ -29,7 +29,7 @@ truncation_level = 1.0 maximum_distance = 200 gsim = BooreAtkinson2008 number_of_ground_motion_fields = 1000 -cross_correlation = GodaAtkinson2009 +cross_imt_correlation_model = GodaAtkinson2009 [output] diff --git a/openquake/qa_tests_data/scenario/case_5/job.ini b/openquake/qa_tests_data/scenario/case_5/job.ini index 85c1a7907b70..dcf50e91fb36 100644 --- a/openquake/qa_tests_data/scenario/case_5/job.ini +++ b/openquake/qa_tests_data/scenario/case_5/job.ini @@ -27,8 +27,8 @@ intensity_measure_types = PGA # km maximum_distance = 200 gsim = BooreAtkinson2008 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} number_of_ground_motion_fields = 65000 truncation_level = 2 diff --git a/openquake/qa_tests_data/scenario/case_6/job.ini b/openquake/qa_tests_data/scenario/case_6/job.ini index 5e2b55c5ce29..2e440b1ddb82 100644 --- a/openquake/qa_tests_data/scenario/case_6/job.ini +++ b/openquake/qa_tests_data/scenario/case_6/job.ini @@ -27,8 +27,8 @@ intensity_measure_types = PGA # km maximum_distance = 200 gsim = BooreAtkinson2008 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} number_of_ground_motion_fields = 20000 truncation_level = 3 diff --git a/openquake/qa_tests_data/scenario/case_7/job.ini b/openquake/qa_tests_data/scenario/case_7/job.ini index 49e76f71ada6..365f249db74f 100644 --- a/openquake/qa_tests_data/scenario/case_7/job.ini +++ b/openquake/qa_tests_data/scenario/case_7/job.ini @@ -27,8 +27,8 @@ intensity_measure_types = PGA # km maximum_distance = 200 gsim = BooreAtkinson2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 65000 diff --git a/openquake/qa_tests_data/scenario/case_8/job.ini b/openquake/qa_tests_data/scenario/case_8/job.ini index 0a217bbfebb3..70df8517a753 100644 --- a/openquake/qa_tests_data/scenario/case_8/job.ini +++ b/openquake/qa_tests_data/scenario/case_8/job.ini @@ -27,8 +27,8 @@ intensity_measure_types = PGA # km maximum_distance = 200 gsim = AtkinsonBoore2003SInter -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 1000 truncation_level = 3 diff --git a/openquake/qa_tests_data/scenario_damage/case_12/job.ini b/openquake/qa_tests_data/scenario_damage/case_12/job.ini index d6c8cd5a4d44..a180448e4f5a 100644 --- a/openquake/qa_tests_data/scenario_damage/case_12/job.ini +++ b/openquake/qa_tests_data/scenario_damage/case_12/job.ini @@ -31,7 +31,7 @@ sec_peril_params = [{}, {"pga_threshold_table": { [Calculation parameters] intensity_measure_types = PGA gsim_logic_tree_file = gmpe_sif.xml -ground_motion_correlation_model = +spatial_correlation_model = truncation_level = 3.0 maximum_distance = 250 number_of_ground_motion_fields = 100 diff --git a/openquake/qa_tests_data/scenario_damage/case_14/job_wrong.ini b/openquake/qa_tests_data/scenario_damage/case_14/job_wrong.ini index a8e13a0dda44..27a42792b31b 100644 --- a/openquake/qa_tests_data/scenario_damage/case_14/job_wrong.ini +++ b/openquake/qa_tests_data/scenario_damage/case_14/job_wrong.ini @@ -25,7 +25,7 @@ reference_depth_to_1pt0km_per_sec = 100.0 [Calculation parameters] gsim_logic_tree_file = gmpe.xml -ground_motion_correlation_model = +spatial_correlation_model = truncation_level = 3.0 maximum_distance = 120 number_of_ground_motion_fields = 100 diff --git a/openquake/qa_tests_data/scenario_damage/case_19/job.ini b/openquake/qa_tests_data/scenario_damage/case_19/job.ini index 616c841247e8..99f05b649023 100644 --- a/openquake/qa_tests_data/scenario_damage/case_19/job.ini +++ b/openquake/qa_tests_data/scenario_damage/case_19/job.ini @@ -15,8 +15,8 @@ rupture_model_file: earthquake_rupture_model_USGS.xml [calculation] intensity_measure_types = SA(0.3), SA(0.6), SA(1.0), PGA gsim_logic_tree_file = gmpe.xml -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} truncation_level = 3.0 maximum_distance = 100.0 number_of_ground_motion_fields = 10 diff --git a/openquake/qa_tests_data/scenario_damage/case_1c/job.ini b/openquake/qa_tests_data/scenario_damage/case_1c/job.ini index 44f4580e1057..c4cbb05718e1 100644 --- a/openquake/qa_tests_data/scenario_damage/case_1c/job.ini +++ b/openquake/qa_tests_data/scenario_damage/case_1c/job.ini @@ -30,8 +30,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] truncation_level = 3.0 diff --git a/openquake/qa_tests_data/scenario_damage/case_20/job.ini b/openquake/qa_tests_data/scenario_damage/case_20/job.ini index b0993daa504c..f7b0f521bb9d 100644 --- a/openquake/qa_tests_data/scenario_damage/case_20/job.ini +++ b/openquake/qa_tests_data/scenario_damage/case_20/job.ini @@ -24,8 +24,8 @@ site_model_file = cipta_Vs30.xml truncation_level = 2.0 maximum_distance = 400 gsim = Bradley2013Volc -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering":False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering":False} number_of_ground_motion_fields = 10 intensity_measure_types = PGA, SA(0.3), SA(0.6) diff --git a/openquake/qa_tests_data/scenario_damage/case_4/job_haz.ini b/openquake/qa_tests_data/scenario_damage/case_4/job_haz.ini index 742a39e97489..50d685c0eb5f 100644 --- a/openquake/qa_tests_data/scenario_damage/case_4/job_haz.ini +++ b/openquake/qa_tests_data/scenario_damage/case_4/job_haz.ini @@ -29,8 +29,8 @@ truncation_level = 3.0 # km maximum_distance = 300 gsim = ChiouYoungs2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 5 diff --git a/openquake/qa_tests_data/scenario_damage/case_4b/job_err.ini b/openquake/qa_tests_data/scenario_damage/case_4b/job_err.ini index 9bbb8abc4f10..b880b87c0624 100644 --- a/openquake/qa_tests_data/scenario_damage/case_4b/job_err.ini +++ b/openquake/qa_tests_data/scenario_damage/case_4b/job_err.ini @@ -17,8 +17,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/scenario_damage/case_4b/job_haz.ini b/openquake/qa_tests_data/scenario_damage/case_4b/job_haz.ini index 275a5924697e..fc37566be4fa 100644 --- a/openquake/qa_tests_data/scenario_damage/case_4b/job_haz.ini +++ b/openquake/qa_tests_data/scenario_damage/case_4b/job_haz.ini @@ -17,8 +17,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/scenario_damage/case_5/job_haz.ini b/openquake/qa_tests_data/scenario_damage/case_5/job_haz.ini index 2a8e69695ad1..b597efa94776 100644 --- a/openquake/qa_tests_data/scenario_damage/case_5/job_haz.ini +++ b/openquake/qa_tests_data/scenario_damage/case_5/job_haz.ini @@ -22,8 +22,8 @@ truncation_level = 3.0 # km maximum_distance = 200 gsim = ChiouYoungs2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 10 [output] diff --git a/openquake/qa_tests_data/scenario_damage/case_5a/job_haz.ini b/openquake/qa_tests_data/scenario_damage/case_5a/job_haz.ini index 700c60b030b8..c46565e1ac62 100644 --- a/openquake/qa_tests_data/scenario_damage/case_5a/job_haz.ini +++ b/openquake/qa_tests_data/scenario_damage/case_5a/job_haz.ini @@ -17,8 +17,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/scenario_damage/case_6/job_h.ini b/openquake/qa_tests_data/scenario_damage/case_6/job_h.ini index 3e423bc91a75..69f9de07d256 100644 --- a/openquake/qa_tests_data/scenario_damage/case_6/job_h.ini +++ b/openquake/qa_tests_data/scenario_damage/case_6/job_h.ini @@ -28,8 +28,8 @@ maximum_distance = 300 # SGC-2010 model uses for all superficial sources Sadigh et al., 1997 gsim = SadighEtAl1997 # The following parameters can be also modified -#ground_motion_correlation_model = JB2009 -#ground_motion_correlation_params = {"vs30_clustering": False} +#spatial_correlation_model = JayaramBaker2009 +#spatial_correlation_params = {"vs30_clustering": False} number_of_ground_motion_fields = 20 [output] diff --git a/openquake/qa_tests_data/scenario_damage/case_7/job_h.ini b/openquake/qa_tests_data/scenario_damage/case_7/job_h.ini index 2ce26b2c5fa2..154191e3e718 100644 --- a/openquake/qa_tests_data/scenario_damage/case_7/job_h.ini +++ b/openquake/qa_tests_data/scenario_damage/case_7/job_h.ini @@ -17,8 +17,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/scenario_damage/case_8/job.ini b/openquake/qa_tests_data/scenario_damage/case_8/job.ini index 7c97b9899eb7..9ef8ed5d39fe 100644 --- a/openquake/qa_tests_data/scenario_damage/case_8/job.ini +++ b/openquake/qa_tests_data/scenario_damage/case_8/job.ini @@ -2,7 +2,6 @@ description = Shakemap Abruzzo calculation_mode = scenario_damage random_seed = 45 -spatial_correlation = no number_of_ground_motion_fields = 25 truncation_level = 3 shakemap_file = usp000246t.npy diff --git a/openquake/qa_tests_data/scenario_damage/case_9/job.ini b/openquake/qa_tests_data/scenario_damage/case_9/job.ini index 5135681300e1..33f0e0a17247 100644 --- a/openquake/qa_tests_data/scenario_damage/case_9/job.ini +++ b/openquake/qa_tests_data/scenario_damage/case_9/job.ini @@ -25,7 +25,7 @@ reference_depth_to_1pt0km_per_sec = 100.0 [Calculation parameters] gsim_logic_tree_file = gmpe.xml -ground_motion_correlation_model = +spatial_correlation_model = truncation_level = 3 maximum_distance = 100.0 number_of_ground_motion_fields = 1000 diff --git a/openquake/qa_tests_data/scenario_risk/case_1g/job_haz.ini b/openquake/qa_tests_data/scenario_risk/case_1g/job_haz.ini index 7649b4f5a718..c4e84305ef10 100644 --- a/openquake/qa_tests_data/scenario_risk/case_1g/job_haz.ini +++ b/openquake/qa_tests_data/scenario_risk/case_1g/job_haz.ini @@ -17,8 +17,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA, SA(0.1), SA(0.3) diff --git a/openquake/qa_tests_data/scenario_risk/case_2d/job_h.ini b/openquake/qa_tests_data/scenario_risk/case_2d/job_h.ini index 2ce26b2c5fa2..154191e3e718 100644 --- a/openquake/qa_tests_data/scenario_risk/case_2d/job_h.ini +++ b/openquake/qa_tests_data/scenario_risk/case_2d/job_h.ini @@ -17,8 +17,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/scenario_risk/case_3/job.ini b/openquake/qa_tests_data/scenario_risk/case_3/job.ini index 662b3b3d9d20..034cab797170 100644 --- a/openquake/qa_tests_data/scenario_risk/case_3/job.ini +++ b/openquake/qa_tests_data/scenario_risk/case_3/job.ini @@ -32,8 +32,8 @@ truncation_level = 3.0 # km maximum_distance = 300 gsim = ChiouYoungs2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 2000 [output] diff --git a/openquake/qa_tests_data/scenario_risk/case_4/job.ini b/openquake/qa_tests_data/scenario_risk/case_4/job.ini index 4a7a1c59e217..ff0c2d4f7a82 100644 --- a/openquake/qa_tests_data/scenario_risk/case_4/job.ini +++ b/openquake/qa_tests_data/scenario_risk/case_4/job.ini @@ -26,8 +26,8 @@ structural_vulnerability_file = structural_vulnerability_model.xml truncation_level = 3.0 maximum_distance = 500 gsim = ChiouYoungs2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 100 [risk_calculation] diff --git a/openquake/qa_tests_data/scenario_risk/case_5/job.ini b/openquake/qa_tests_data/scenario_risk/case_5/job.ini index fc090ebf52ae..d0bf492d05c8 100644 --- a/openquake/qa_tests_data/scenario_risk/case_5/job.ini +++ b/openquake/qa_tests_data/scenario_risk/case_5/job.ini @@ -21,6 +21,6 @@ structural_vulnerability_file = structural_vulnerability_model.xml truncation_level = 3.0 maximum_distance = 200 gsim = AkkarEtAlRjb2014 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} number_of_ground_motion_fields = 100 diff --git a/openquake/qa_tests_data/scenario_risk/case_6a/job_haz.ini b/openquake/qa_tests_data/scenario_risk/case_6a/job_haz.ini index 11088cc6aade..19f2c2a6fc36 100644 --- a/openquake/qa_tests_data/scenario_risk/case_6a/job_haz.ini +++ b/openquake/qa_tests_data/scenario_risk/case_6a/job_haz.ini @@ -17,8 +17,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [calculation] intensity_measure_types = PGA diff --git a/openquake/qa_tests_data/scenario_risk/case_master/job.ini b/openquake/qa_tests_data/scenario_risk/case_master/job.ini index bc318f092a1f..3f19a5c816ae 100644 --- a/openquake/qa_tests_data/scenario_risk/case_master/job.ini +++ b/openquake/qa_tests_data/scenario_risk/case_master/job.ini @@ -20,8 +20,8 @@ reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 [correlation] -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [hazard_calculation] random_seed = 42 diff --git a/openquake/qa_tests_data/scenario_risk/case_shakemap/job.ini b/openquake/qa_tests_data/scenario_risk/case_shakemap/job.ini index 3e63ad0fd69a..177f643ae7d2 100644 --- a/openquake/qa_tests_data/scenario_risk/case_shakemap/job.ini +++ b/openquake/qa_tests_data/scenario_risk/case_shakemap/job.ini @@ -6,8 +6,9 @@ number_of_ground_motion_fields = 3 shakemap_uri = {"kind": "file_npy", "fname": "shakefile/usp000fjta.npy"} asset_hazard_distance = 20 truncation_level = 3 -cross_correlation = yes -spatial_correlation = yes +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} +cross_imt_correlation_model = BakerCornell2006 maximum_distance = 200 secondary_perils = AllstadtEtAl2022Landslides, AllstadtEtAl2022Liquefaction intensity_measure_types = PGA, PGV, SA(0.3), SA(1.0) diff --git a/openquake/qa_tests_data/scenario_risk/case_shapefile/job.ini b/openquake/qa_tests_data/scenario_risk/case_shapefile/job.ini index e0471d70783f..c812509bb338 100755 --- a/openquake/qa_tests_data/scenario_risk/case_shapefile/job.ini +++ b/openquake/qa_tests_data/scenario_risk/case_shapefile/job.ini @@ -6,6 +6,4 @@ number_of_ground_motion_fields = 3 shakemap_uri = { "kind": "shapefile", "fname": "shp/output.shp"} -spatial_correlation = no -cross_correlation = no truncation_level = 2 diff --git a/openquake/qa_tests_data/scenario_risk/case_shapefile/job_zipped.ini b/openquake/qa_tests_data/scenario_risk/case_shapefile/job_zipped.ini index 1121ce388cdf..1e9163e6e11c 100755 --- a/openquake/qa_tests_data/scenario_risk/case_shapefile/job_zipped.ini +++ b/openquake/qa_tests_data/scenario_risk/case_shapefile/job_zipped.ini @@ -6,6 +6,4 @@ number_of_ground_motion_fields = 3 shakemap_uri = { "kind": "shapefile", "fname": "shp/shapefiles.zip"} -spatial_correlation = no -cross_correlation = no truncation_level = 3 diff --git a/openquake/qa_tests_data/scenario_risk/conditioned/job.ini b/openquake/qa_tests_data/scenario_risk/conditioned/job.ini index 223e60975892..cc03251bdcbf 100644 --- a/openquake/qa_tests_data/scenario_risk/conditioned/job.ini +++ b/openquake/qa_tests_data/scenario_risk/conditioned/job.ini @@ -13,13 +13,13 @@ rupture_model_file = earthquake_rupture_model_GCMT.xml [calculation] intensity_measure_types = PGA, SA(0.3), SA(0.6), SA(1.0) -cross_correlation = GodaAtkinson2009 +cross_imt_correlation_model = GodaAtkinson2009 truncation_level = 3.0 maximum_distance = 200 number_of_ground_motion_fields = 5 gsim_logic_tree_file = gmpe_logic_tree_GEM_best_all_filtered_50_cataldi.xml -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} extreme_gmv = { "PGA": 2.5, "SA(0.3)": 3.5, "SA(0.6)": 3.0, "SA(1.0)": 1.5} with_betw_ratio = 1.7 diff --git a/openquake/qa_tests_data/scenario_risk/contents/job.ini b/openquake/qa_tests_data/scenario_risk/contents/job.ini index 6e0773447d8e..5de4b92ea075 100644 --- a/openquake/qa_tests_data/scenario_risk/contents/job.ini +++ b/openquake/qa_tests_data/scenario_risk/contents/job.ini @@ -22,6 +22,6 @@ rupture_model_file = fault_rupture.xml truncation_level = 3.0 maximum_distance = 300 gsim = ChiouYoungs2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 10 diff --git a/openquake/qa_tests_data/scenario_risk/contents/job_haz.ini b/openquake/qa_tests_data/scenario_risk/contents/job_haz.ini index de39f119e37d..13b59416c5b6 100644 --- a/openquake/qa_tests_data/scenario_risk/contents/job_haz.ini +++ b/openquake/qa_tests_data/scenario_risk/contents/job_haz.ini @@ -29,6 +29,6 @@ truncation_level = 3.0 # km maximum_distance = 300 gsim = ChiouYoungs2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 10 diff --git a/openquake/qa_tests_data/scenario_risk/occupants/job_haz.ini b/openquake/qa_tests_data/scenario_risk/occupants/job_haz.ini index e0920026ae9a..0702a5a0cf37 100644 --- a/openquake/qa_tests_data/scenario_risk/occupants/job_haz.ini +++ b/openquake/qa_tests_data/scenario_risk/occupants/job_haz.ini @@ -29,6 +29,6 @@ truncation_level = 3.0 # km maximum_distance = 300 gsim = ChiouYoungs2008 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = number_of_ground_motion_fields = 10 diff --git a/openquake/server/papers/base.py b/openquake/server/papers/base.py index b27bb6af4079..209fb1f59a58 100644 --- a/openquake/server/papers/base.py +++ b/openquake/server/papers/base.py @@ -118,9 +118,9 @@ def get_job_ctx(rup_id, 'number_of_ground_motion_fields': str(num_gmfs), # NOTE: correlation disabled to avoid too big # calculations and suppression of avg_gmf outputs - # 'cross_correlation': 'GodaAtkinson2009', - # 'ground_motion_correlation_model': 'JB2009', - # 'ground_motion_correlation_params': '{"vs30_clustering":True}', + # 'cross_imt_correlation_model': 'GodaAtkinson2009', + # 'spatial_correlation_model': 'JayaramBaker2009', + # 'spatial_correlation_params': '{"vs30_clustering":True}', 'horiz_comp_to_geom_mean': 'true', # 'time_event': day_or_night, 'export_dir': '/tmp', @@ -146,9 +146,9 @@ def get_job_ctx(rup_id, # Correlation requires non-zero truncation (it's set to zero # in hazard only) given we just want the median ground-motion - for key in ['cross_correlation', - 'ground_motion_correlation_model', - 'ground_motion_correlation_params']: + for key in ['cross_imt_correlation_model', + 'spatial_correlation_model', + 'spatial_correlation_params']: del job_dict[key] [job] = engine.create_jobs([job_dict], config.distribution.log_level, None, diff --git a/openquake/server/tests/data/db/models_test/event-based-job.ini b/openquake/server/tests/data/db/models_test/event-based-job.ini index 9a14313e5f46..6a76be18ae41 100644 --- a/openquake/server/tests/data/db/models_test/event-based-job.ini +++ b/openquake/server/tests/data/db/models_test/event-based-job.ini @@ -42,8 +42,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 3 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [output] diff --git a/openquake/server/tests/data/event_based_hazard/job.ini b/openquake/server/tests/data/event_based_hazard/job.ini index a64580b37655..36769e1537c1 100644 --- a/openquake/server/tests/data/event_based_hazard/job.ini +++ b/openquake/server/tests/data/event_based_hazard/job.ini @@ -45,8 +45,8 @@ minimum_intensity = .01 [event_based_params] ses_per_logic_tree_path = 5 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [output] diff --git a/openquake/server/tests/data/event_based_hazard/job_2.ini b/openquake/server/tests/data/event_based_hazard/job_2.ini index b15b9a93f2cb..5c6d92fcac67 100644 --- a/openquake/server/tests/data/event_based_hazard/job_2.ini +++ b/openquake/server/tests/data/event_based_hazard/job_2.ini @@ -43,8 +43,8 @@ maximum_distance = 200.0 [event_based_params] ses_per_logic_tree_path = 5 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": True} [output] diff --git a/openquake/server/tests/data/job.ini b/openquake/server/tests/data/job.ini index 0742e73f87e3..abbf844756d3 100644 --- a/openquake/server/tests/data/job.ini +++ b/openquake/server/tests/data/job.ini @@ -45,8 +45,8 @@ maximum_distance = 300.0 [event_based_params] ses_per_logic_tree_path = 5 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/server/tests/data/job_haz.ini b/openquake/server/tests/data/job_haz.ini index f4d57003c7e2..a4e48a84b650 100644 --- a/openquake/server/tests/data/job_haz.ini +++ b/openquake/server/tests/data/job_haz.ini @@ -47,10 +47,10 @@ maximum_distance = 300.0 [event_based_params] ses_per_logic_tree_path = 100 -ground_motion_correlation_model = -ground_motion_correlation_params = -#ground_motion_correlation_model = JB2009 -#ground_motion_correlation_params = {"vs30_clustering": True} +spatial_correlation_model = +spatial_correlation_params = +#spatial_correlation_model = JayaramBaker2009 +#spatial_correlation_params = {"vs30_clustering": True} [output] diff --git a/openquake/server/tests/data/job_invalid.ini b/openquake/server/tests/data/job_invalid.ini index a16d31b4a364..9d6689bd2212 100644 --- a/openquake/server/tests/data/job_invalid.ini +++ b/openquake/server/tests/data/job_invalid.ini @@ -45,8 +45,8 @@ maximum_distance = 300.0 [event_based_params] ses_per_logic_tree_path = 5 -ground_motion_correlation_model = -ground_motion_correlation_params = +spatial_correlation_model = +spatial_correlation_params = [output] diff --git a/openquake/server/tests/data/scenario_hazard/job.ini b/openquake/server/tests/data/scenario_hazard/job.ini index 5e180a044f36..a8e45603e08f 100644 --- a/openquake/server/tests/data/scenario_hazard/job.ini +++ b/openquake/server/tests/data/scenario_hazard/job.ini @@ -28,8 +28,8 @@ truncation_level = 1.0 # km maximum_distance = 200 gsim = BooreAtkinson2008 -ground_motion_correlation_model = JB2009 -ground_motion_correlation_params = {"vs30_clustering": False} +spatial_correlation_model = JayaramBaker2009 +spatial_correlation_params = {"vs30_clustering": False} number_of_ground_motion_fields = 10