From 35d0cdf5b640a867e3bf1f5d399ce00f207dbe68 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 06:19:34 +0200 Subject: [PATCH 1/6] Unified cost_type with loss_type --- openquake/hazardlib/valid.py | 3 --- .../scenario/case_16/Example_Exposure.xml | 2 +- openquake/risklib/asset.py | 10 ++-------- openquake/risklib/read_nrml.py | 12 ++++++------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/openquake/hazardlib/valid.py b/openquake/hazardlib/valid.py index 26213da1a91d..a70dd945b47d 100644 --- a/openquake/hazardlib/valid.py +++ b/openquake/hazardlib/valid.py @@ -1209,9 +1209,6 @@ def host_port(value=None): # used for the exposure validation -cost_type = Choice('structural', 'nonstructural', 'contents', - 'business_interruption') - cost_type_type = Choice('aggregated', 'per_area', 'per_asset') diff --git a/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml b/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml index 64ba9a291d99..a75976ec26e3 100644 --- a/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml +++ b/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml @@ -6,8 +6,8 @@ A flexible exposure model - + diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index 8bf51c87befb..0148e4171921 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -147,6 +147,7 @@ def __call__(self, loss_type, assetcol): return cost * area elif area_type == "per_asset": return cost * area * number + breakpoint() # this should never happen raise RuntimeError('Unable to compute cost for %r' % loss_type) @@ -587,14 +588,7 @@ def get_other_fields(fields): def _get_exposure(fname, stop=None): - """ - :param fname: - path of the XML file containing the exposure - :param stop: - node at which to stop parsing (or None) - :returns: - a pair (Exposure instance, list of asset nodes) - """ + # returns Exposure instance, list of asset nodes [xml] = nrml.read(fname, stop=stop) if not xml.tag.endswith('exposureModel'): raise InvalidFile('%s: expected exposureModel, got %s' % diff --git a/openquake/risklib/read_nrml.py b/openquake/risklib/read_nrml.py index 73218a4e9615..e6c3eff04861 100644 --- a/openquake/risklib/read_nrml.py +++ b/openquake/risklib/read_nrml.py @@ -352,8 +352,8 @@ def get_fragility_model_04(fmodel, fname): # ######################## validators ######################## # -valid_loss_types = valid.Choice('structural', 'nonstructural', 'contents', - 'business_interruption', 'occupants') +valid_loss_type = valid.Choice(*[lt for lt in scientific.LOSSTYPE + if '+' not in lt and '_ins' not in lt]) def asset_mean_stddev(value, assetRef, mean, stdDev): @@ -386,9 +386,9 @@ def update_validators(): 'vulnerabilityFunction.id': valid.risk_id, # taxonomy 'consequenceFunction.id': valid.risk_id, # taxonomy 'asset.id': valid.asset_id, - 'costType.name': valid.cost_type, + 'costType.name': valid_loss_type, 'costType.type': valid.cost_type_type, - 'cost.type': valid.cost_type, + 'cost.type': valid_loss_type, 'area.type': valid.name, 'isAbsolute': valid.boolean, 'insuranceLimit': valid.positivefloat, @@ -416,14 +416,14 @@ def update_validators(): 'maxIML': valid.positivefloat, 'limitStates': valid.namelist, 'noDamageLimit': valid.NoneOr(valid.positivefloat), - 'loss_type': valid_loss_types, + 'loss_type': valid_loss_type, 'losses': valid.positivefloats, 'averageLoss': valid.positivefloat, 'stdDevLoss': valid.positivefloat, 'ffs.type': valid.ChoiceCI('lognormal'), 'assetLifeExpectancy': valid.positivefloat, 'interestRate': valid.positivefloat, - 'lossType': valid_loss_types, + 'lossType': valid_loss_type, 'aalOrig': valid.positivefloat, 'aalRetr': valid.positivefloat, 'ratio': valid.positivefloat, From 3895d77dc7ac5f771d0ed50eef77820626e4324c Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 06:21:30 +0200 Subject: [PATCH 2/6] Removed accidental breakpoint --- openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml | 2 +- openquake/risklib/asset.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml b/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml index a75976ec26e3..64ba9a291d99 100644 --- a/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml +++ b/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml @@ -6,8 +6,8 @@ A flexible exposure model + - diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index 0148e4171921..b04bd56e41bd 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -147,7 +147,6 @@ def __call__(self, loss_type, assetcol): return cost * area elif area_type == "per_asset": return cost * area * number - breakpoint() # this should never happen raise RuntimeError('Unable to compute cost for %r' % loss_type) From e464efe6b9b881e73ff2a5889082dde0b27d2e5d Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 06:35:36 +0200 Subject: [PATCH 3/6] Accepting area as costType --- .../scenario/case_16/Example_Exposure.xml | 2 +- openquake/risklib/asset.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml b/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml index 64ba9a291d99..a75976ec26e3 100644 --- a/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml +++ b/openquake/qa_tests_data/scenario/case_16/Example_Exposure.xml @@ -6,8 +6,8 @@ A flexible exposure model - + diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index b04bd56e41bd..ada9f03625df 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -565,6 +565,12 @@ def __repr__(self): ('type', hdf5.vstr), ('unit', hdf5.vstr)]) +def get_area_type(cost_types): + ct = cost_types[cost_types['name'] == 'area'] + if len(ct) == 0: + return '?' + return ct['type'][0] + # The fields in the exposure are complicated. For the global # risk model you will have things like the following: @@ -613,11 +619,7 @@ def _get_exposure(fname, stop=None): try: area = conversions.area except AttributeError: - # NB: the area type cannot be an empty string because when sending - # around the CostCalculator object we would run into this numpy bug - # about pickling dictionaries with empty strings: - # https://github.com/numpy/numpy/pull/5475 - area = Node('area', dict(type='?')) + area = Node('area', dict(type='')) try: occupancy_periods = xml.occupancyPeriods.text.split() except AttributeError: @@ -663,7 +665,7 @@ def _get_exposure(fname, stop=None): for ct in cost_types: name = ct['name'] # structural, nonstructural, ... cc.cost_types[name] = ct['type'] # aggregated, per_asset, per_area - cc.area_types[name] = area['type'] + cc.area_types[name] = area['type'] or get_area_type(cost_types) cc.units[name] = ct['unit'] exp = Exposure(occupancy_periods, area.attrib, [], cc, TagCollection(tagnames), pairs) From f96468cbe70fe10fd5dd531a3039c617b334f4b5 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 06:39:01 +0200 Subject: [PATCH 4/6] Removed field area from Exposure --- openquake/risklib/asset.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index ada9f03625df..340a2a2111e4 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -616,10 +616,6 @@ def _get_exposure(fname, stop=None): pairs.append((node['input'], noq)) except AttributeError: pass # no fieldmap - try: - area = conversions.area - except AttributeError: - area = Node('area', dict(type='')) try: occupancy_periods = xml.occupancyPeriods.text.split() except AttributeError: @@ -665,10 +661,9 @@ def _get_exposure(fname, stop=None): for ct in cost_types: name = ct['name'] # structural, nonstructural, ... cc.cost_types[name] = ct['type'] # aggregated, per_asset, per_area - cc.area_types[name] = area['type'] or get_area_type(cost_types) + cc.area_types[name] = get_area_type(cost_types) cc.units[name] = ct['unit'] - exp = Exposure(occupancy_periods, area.attrib, [], cc, - TagCollection(tagnames), pairs) + exp = Exposure(occupancy_periods, [], cc, TagCollection(tagnames), pairs) assets_text = xml.assets.text.strip() if assets_text: # the tag contains a list of file names @@ -882,7 +877,7 @@ class Exposure(object): """ A class to read the exposure from XML/CSV files """ - fields = ['occupancy_periods', 'area', 'assets', + fields = ['occupancy_periods', 'assets', 'cost_calculator', 'tagcol', 'pairs'] def __toh5__(self): From 806a8f5ca3bccea966b5b15272e6218962b3a8ad Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 06:48:14 +0200 Subject: [PATCH 5/6] Removed area_types --- openquake/risklib/asset.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index 340a2a2111e4..27de76792cf0 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -103,16 +103,10 @@ class CostCalculator(object): The same "formula" applies to retrofitting cost. """ - def __init__(self, cost_types, area_types, units, tagi={'taxonomy': 0}): - if set(cost_types) != set(area_types): - raise ValueError('cost_types has keys %s, area_types has keys %s' - % (sorted(cost_types), sorted(area_types))) + def __init__(self, cost_types, units, tagi={'taxonomy': 0}): for ct in cost_types.values(): assert ct in ('aggregated', 'per_asset', 'per_area'), ct - for at in area_types.values(): - assert at in ('aggregated', 'per_asset'), at self.cost_types = cost_types - self.area_types = area_types self.units = units self.tagi = tagi @@ -142,7 +136,7 @@ def __call__(self, loss_type, assetcol): if cost_type == "per_asset": return cost * number if cost_type == "per_area": - area_type = self.area_types[loss_type] + area_type = self.cost_types['area'] if area_type == "aggregated": return cost * area elif area_type == "per_asset": @@ -657,11 +651,10 @@ def _get_exposure(fname, stop=None): cost_types.sort(key=operator.itemgetter(0)) cost_types = numpy.array(cost_types, cost_type_dt) cc = CostCalculator( - {}, {}, {}, {name: i for i, name in enumerate(tagnames)}) + {}, {}, {name: i for i, name in enumerate(tagnames)}) for ct in cost_types: name = ct['name'] # structural, nonstructural, ... cc.cost_types[name] = ct['type'] # aggregated, per_asset, per_area - cc.area_types[name] = get_area_type(cost_types) cc.units[name] = ct['unit'] exp = Exposure(occupancy_periods, [], cc, TagCollection(tagnames), pairs) assets_text = xml.assets.text.strip() @@ -883,12 +876,9 @@ class Exposure(object): def __toh5__(self): cc = self.cost_calculator loss_types = sorted(cc.cost_types) - dt = numpy.dtype([('cost_type', hdf5.vstr), - ('area_type', hdf5.vstr), - ('unit', hdf5.vstr)]) + dt = numpy.dtype([('cost_type', hdf5.vstr), ('unit', hdf5.vstr)]) array = numpy.zeros(len(loss_types), dt) array['cost_type'] = [cc.cost_types[lt] for lt in loss_types] - array['area_type'] = [cc.area_types[lt] for lt in loss_types] array['unit'] = [cc.units[lt] for lt in loss_types] attrs = dict( loss_types=hdf5.array_of_vstr(loss_types), @@ -900,7 +890,6 @@ def __fromh5__(self, array, attrs): vars(self).update(attrs) cc = self.cost_calculator = object.__new__(CostCalculator) cc.cost_types = dict(zip(self.loss_types, decode(array['cost_type']))) - cc.area_types = dict(zip(self.loss_types, decode(array['area_type']))) cc.units = dict(zip(self.loss_types, decode(array['unit']))) @staticmethod From 01b68d72cb7b8d82e7d4a352902d73b79f08fea1 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 06:55:20 +0200 Subject: [PATCH 6/6] Removed unused code [ci skip] --- openquake/risklib/asset.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index 27de76792cf0..cf036ee51327 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -559,12 +559,6 @@ def __repr__(self): ('type', hdf5.vstr), ('unit', hdf5.vstr)]) -def get_area_type(cost_types): - ct = cost_types[cost_types['name'] == 'area'] - if len(ct) == 0: - return '?' - return ct['type'][0] - # The fields in the exposure are complicated. For the global # risk model you will have things like the following: @@ -587,7 +581,7 @@ def get_other_fields(fields): def _get_exposure(fname, stop=None): - # returns Exposure instance, list of asset nodes + # returns (Exposure instance, asset nodes) [xml] = nrml.read(fname, stop=stop) if not xml.tag.endswith('exposureModel'): raise InvalidFile('%s: expected exposureModel, got %s' %