From c8d872fe567b011b4ff235d7a171b50e79432b89 Mon Sep 17 00:00:00 2001 From: Lana Todorovic Date: Mon, 27 Jul 2026 18:55:58 +0200 Subject: [PATCH 1/3] tsunami risk (scenario) from precomputed hazard --- openquake/commonlib/oqvalidation.py | 2 ++ openquake/hazardlib/imt.py | 24 +++++++++++++++- openquake/risklib/riskmodels.py | 16 +++++++---- openquake/risklib/scientific.py | 5 ++-- openquake/sep/classes.py | 44 +++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 8 deletions(-) diff --git a/openquake/commonlib/oqvalidation.py b/openquake/commonlib/oqvalidation.py index 0bdf040c8822..81d47dfdbc13 100644 --- a/openquake/commonlib/oqvalidation.py +++ b/openquake/commonlib/oqvalidation.py @@ -1067,6 +1067,8 @@ class OqParam(valid.ParamSet): 'liquefaction_vulnerability', 'landslide_fragility', 'landslide_vulnerability', + 'tsunami_fragility', + 'tsunami_vulnerability', 'post_loss_amplification', } | {vtype + '_vulnerability' for vtype in VULN_TYPES} # old name => new name diff --git a/openquake/hazardlib/imt.py b/openquake/hazardlib/imt.py index efeb2025c637..c8174258ba29 100644 --- a/openquake/hazardlib/imt.py +++ b/openquake/hazardlib/imt.py @@ -318,6 +318,7 @@ def JMA(): # secondary IMTs sec_imts = '''ASH LAVA LAHAR PYRO Disp DispProb LiqProb LiqOccur LSE PGDMax PGDGeomMean LsProb +FlowDepth FlowVel Flux '''.split() for sec in sec_imts: assert '_' not in sec, sec @@ -353,7 +354,7 @@ def PYRO(): return IMT('PYRO') -# Liquefaction IMTs +# Liquefaction and Landslide IMTs def Disp(): """ @@ -417,3 +418,24 @@ def LsProb(): Probability of landsliding. """ return IMT('LsProb') + + +# Tsunami IMTs + +def FlowDepth(): + """ + Flow depth in meters. + """ + return IMT('FlowDepth') + +def FlowVel(): + """ + Flow velocity in m/s. + """ + return IMT('FlowVel') + +def Flux(): + """ + Flow flux in m^3/s. + """ + return IMT('Flux') diff --git a/openquake/risklib/riskmodels.py b/openquake/risklib/riskmodels.py index 13e0e83766ce..0a1f02f79ff0 100644 --- a/openquake/risklib/riskmodels.py +++ b/openquake/risklib/riskmodels.py @@ -240,7 +240,10 @@ def __getitem__(self, lt): if isinstance(lt, tuple): return dict.__getitem__(self, lt) else: # assume lt is a loss_type string - return dict.__getitem__(self, ('groundshaking', lt)) + for key in self: + if key[1] == lt: + return dict.__getitem__(self, key) + raise KeyError(('groundshaking', lt)) class RiskModel(object): @@ -285,7 +288,9 @@ def loss_types(self): The list of loss types in the underlying vulnerability functions, in lexicographic order """ - return sorted(self.risk_functions['groundshaking']) + for peril in self.risk_functions: + return sorted(self.risk_functions[peril]) + return [] def __call__(self, assets, gmf_df, rndgen=None): meth = getattr(self, self.calcmode) @@ -299,7 +304,7 @@ def __toh5__(self): def __fromh5__(self, dic, attrs): vars(self).update(attrs) - assert 'groundshaking' in dic, list(dic) + assert len(dic) > 0, list(dic) self.risk_functions = dic def __repr__(self): @@ -782,8 +787,9 @@ def init(self): iml = collections.defaultdict(list) # ._riskmodels is empty if read from the hazard calculation for riskid, rm in self._riskmodels.items(): - for lt, rf in rm.risk_functions['groundshaking'].items(): - iml[rf.imt].append(rf.imls[0]) + for peril in rm.risk_functions: + for lt, rf in rm.risk_functions[peril].items(): + iml[rf.imt].append(rf.imls[0]) if oq.impact: pass # don't set minimum_intensity diff --git a/openquake/risklib/scientific.py b/openquake/risklib/scientific.py index 192b28cc64b7..6f20636a8c42 100644 --- a/openquake/risklib/scientific.py +++ b/openquake/risklib/scientific.py @@ -45,7 +45,7 @@ 'losses', 'collapsed', 'injured', 'fatalities', 'homeless', 'non_operational'] -PERILTYPE = numpy.array(['groundshaking', 'liquefaction', 'landslide']) +PERILTYPE = numpy.array(['groundshaking', 'liquefaction', 'landslide', 'tsunami']) LOSSTYPE = numpy.array('''\ business_interruption contents nonstructural structural occupants occupants_day occupants_night occupants_transit @@ -1687,7 +1687,8 @@ def output(self, asset_df, haz, sec_losses=(), rndgen=None): """ dic = collections.defaultdict(list) # peril, lt -> outs weights = collections.defaultdict(list) # peril, lt -> weights - perils = {'groundshaking'} + # perils = {'groundshaking'} + perils = set() for riskid, rm in self.items(): for (peril, lt), res in rm(asset_df, haz, rndgen).items(): # res is an array of fractions of shape (A, E, D) diff --git a/openquake/sep/classes.py b/openquake/sep/classes.py index a82a16a670ec..52c949078744 100644 --- a/openquake/sep/classes.py +++ b/openquake/sep/classes.py @@ -1021,6 +1021,48 @@ def compute(self, mag, imt_gmf, sites): return [] +class Tsunami(SecondaryPeril): + """ + Import FlowDepth, FlowVel, Flux from CSV files + """ + peril = 'tsunami' + inputs = [] + outputs = ["FlowDepth", "FlowVel", "Flux"] + + def prepare(self, sites=None): + """ + Import the CSV files for tsunami subperils. + """ + if sites is None: + return + if 'multi_peril' not in self.oq.inputs: + return + for peril in self.oq.inputs['multi_peril']: + assert peril in self.outputs, peril + self.fname_by_peril = self.oq.inputs['multi_peril'] + N = len(sites) + self.data = {'sid': sites.sids, 'eid': numpy.zeros(N, numpy.uint32)} + names = [] + for name, fname in self.fname_by_peril.items(): + fname = os.path.join(self.oq.base_path, fname) + tofloat = valid.positivefloat + with open(fname) as f: + header = next(f) + if 'geom' in header: + peril = wkt2peril(fname, name, sites) + else: + peril = csv2peril(fname, name, sites, tofloat, + self.oq.asset_hazard_distance) + if peril.sum() == 0: + logging.warning('No sites were affected by %s' % name) + self.data[f'{self.__class__.__name__}_{name}'] = peril + names.append(name) + + def compute(self, mag, imt_gmf, sites): + # doing nothing, since all the work is in the `prepare` method + return [] + + LIQUEFACTION_MODELS = {cls.__name__ for cls in SecondaryPeril.__subclasses__() if cls.peril == 'liquefaction'} LANDSLIDE_MODELS = {cls.__name__ for cls in SecondaryPeril.__subclasses__() @@ -1035,6 +1077,8 @@ def corresponds(col, peril, imt): return False if peril == 'groundshaking': return True + if peril == 'tsunami': + return True name, _ = col.split('_') if peril == 'liquefaction': return name in LIQUEFACTION_MODELS From 6fde862fd2f4f65d1b4f137fd88e0c99e4881cda Mon Sep 17 00:00:00 2001 From: Lana Todorovic Date: Mon, 27 Jul 2026 22:34:24 +0200 Subject: [PATCH 2/3] initialize perils from wdic --- openquake/risklib/scientific.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openquake/risklib/scientific.py b/openquake/risklib/scientific.py index 6f20636a8c42..b667161f4204 100644 --- a/openquake/risklib/scientific.py +++ b/openquake/risklib/scientific.py @@ -1688,7 +1688,7 @@ def output(self, asset_df, haz, sec_losses=(), rndgen=None): dic = collections.defaultdict(list) # peril, lt -> outs weights = collections.defaultdict(list) # peril, lt -> weights # perils = {'groundshaking'} - perils = set() + perils = {peril for _, peril in self.wdic} for riskid, rm in self.items(): for (peril, lt), res in rm(asset_df, haz, rndgen).items(): # res is an array of fractions of shape (A, E, D) From 63241645692afab68632401e428a7f3f7461df39 Mon Sep 17 00:00:00 2001 From: Lana Todorovic Date: Mon, 27 Jul 2026 23:21:15 +0200 Subject: [PATCH 3/3] further fix --- openquake/risklib/scientific.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openquake/risklib/scientific.py b/openquake/risklib/scientific.py index b667161f4204..cbf769790a92 100644 --- a/openquake/risklib/scientific.py +++ b/openquake/risklib/scientific.py @@ -1688,7 +1688,7 @@ def output(self, asset_df, haz, sec_losses=(), rndgen=None): dic = collections.defaultdict(list) # peril, lt -> outs weights = collections.defaultdict(list) # peril, lt -> weights # perils = {'groundshaking'} - perils = {peril for _, peril in self.wdic} + perils = {peril for _, peril in self.wdic} or {'groundshaking'} for riskid, rm in self.items(): for (peril, lt), res in rm(asset_df, haz, rndgen).items(): # res is an array of fractions of shape (A, E, D)