From 04952993806a9d234d995d34ce0b7699f2a3777f Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 16 Oct 2024 14:43:43 +0200 Subject: [PATCH 01/15] Added export_site_model_csv --- openquake/calculators/export/hazard.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openquake/calculators/export/hazard.py b/openquake/calculators/export/hazard.py index 6546e639f7e6..03a0f245df91 100644 --- a/openquake/calculators/export/hazard.py +++ b/openquake/calculators/export/hazard.py @@ -456,6 +456,16 @@ def export_gmf_data_csv(ekey, dstore): return [fname, f] +@export.add(('site_model', 'csv')) +def export_site_model_csv(ekey, dstore): + sitecol = dstore['sitecol'] + fname = dstore.build_fname(ekey[0], '', ekey[1]) + writers.CsvWriter(fmt=writers.FIVEDIGITS).save( + sitecol.array, fname, comment=dstore.metadata) + return [fname] + + + @export.add(('gmf_data', 'hdf5')) def export_gmf_data_hdf5(ekey, dstore): fname = dstore.build_fname('gmf', 'data', 'hdf5') From 680757ae66af836a991ec6bb0a7f2d648ef8fb0f Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 16 Oct 2024 15:14:17 +0200 Subject: [PATCH 02/15] Added export_vulnerability_xml --- openquake/calculators/export/risk.py | 35 +++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index f74123faef14..ef8ee534aab9 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -21,8 +21,9 @@ import numpy import pandas -from openquake.baselib import hdf5, writers, general +from openquake.baselib import hdf5, writers, general, node from openquake.baselib.python3compat import decode +from openquake.hazardlib import nrml from openquake.hazardlib.stats import compute_stats2 from openquake.risklib import scientific from openquake.calculators.extract import ( @@ -654,6 +655,7 @@ def export_aggcurves_csv(ekey, dstore): return fnames +# TODO: rename to exposure and export both exposure.csv and exposure.xml @export.add(('assetcol', 'csv')) def export_assetcol_csv(ekey, dstore): """ @@ -720,3 +722,34 @@ def export_node_el(ekey, dstore): writer = writers.CsvWriter(fmt=writers.FIVEDIGITS) writer.save(df, dest, comment=dstore.metadata) return writer.getsaved() + + +def convert_df_to_vulnerability(loss_type, df): + N = node.Node + root = N('vulnerabilityModel', {'id': "vulnerability_model", + 'assetCategory': "buildings", + "lossCategory": loss_type}) + descr = N('description', {}, f"{loss_type} vulnerability model") + root.append(descr) + for riskfunc in df.riskfunc: + rfunc = json.loads(riskfunc)['openquake.risklib.scientific.VulnerabilityFunction'] + vfunc = N('vulnerabilityFunction', + {'id': rfunc['id'], 'dist': rfunc['distribution_name']}) + imls = N('imls', {'imt': rfunc['imt']}, rfunc['imls']) + vfunc.append(imls) + vfunc.append(N('meanLRs', {}, rfunc['mean_loss_ratios'])) + vfunc.append(N('covLRs', {}, rfunc['covs'])) + root.append(vfunc) + return root + + +@export.add(('vulnerability', 'xml')) +def export_vulnerability_xml(ekey, dstore): + fnames = [] + for loss_type, df in dstore.read_df('crm').groupby('loss_type'): + nodeobj = convert_df_to_vulnerability(loss_type, df) + dest = dstore.export_path('%s_%s.%s' % ((loss_type,) + ekey)) + with open(dest, 'wb') as out: + nrml.write([nodeobj], out) + fnames.append(dest) + return fnames From 54415f697d56d4608bd87c21232b0ff429dbd01a Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 16 Oct 2024 15:33:01 +0200 Subject: [PATCH 03/15] Added export_job_zip --- openquake/calculators/export/risk.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index ef8ee534aab9..53b7b9ece833 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -753,3 +753,37 @@ def export_vulnerability_xml(ekey, dstore): nrml.write([nodeobj], out) fnames.append(dest) return fnames + + +@export.add(('job', 'zip')) +def export_job_zip(ekey, dstore): + """ + Exports: + - job.ini + - rupture.csv + - gsim_lt.xml + - site_model.csv + - exposure.xml and exposure.csv + - vulnerability functions.xml + - taxonomy_mapping.csv + """ + oq = dstore['oqparam'] + job_ini = dstore.export_path('job.ini') + with open(job_ini, 'w') as out: + out.write(oq.to_ini()) + fnames = [job_ini] + # fnames.extend(export(('ruptures', 'csv'), dstore)) TODO + gsim_lt = dstore['full_lt'].gsim_lt + dest = dstore.export_path('gsim_logic_tree.xml') + with open(dest, 'wb') as out: + nrml.write([gsim_lt.to_node()], out) + fnames.append(dest) + fnames.extend(export(('vulnerability', 'xml'), dstore)) + + dest = dstore.export_path('taxonomy_mapping.csv') + taxmap = dstore.read_df('taxmap') + writer = writers.CsvWriter(fmt=writers.FIVEDIGITS) + del taxmap['taxi'] + writer.save(taxmap, dest) + fnames.append(dest) + return fnames From 098b2d62b548dff083caa58bbc398b5a0d06f774 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Thu, 17 Oct 2024 08:30:00 +0200 Subject: [PATCH 04/15] Refined OqParam.to_ini() --- openquake/calculators/event_based_risk.py | 2 -- openquake/commonlib/oqvalidation.py | 9 +++------ openquake/hazardlib/contexts.py | 10 +++++----- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/openquake/calculators/event_based_risk.py b/openquake/calculators/event_based_risk.py index b733b1b77558..b978e6dfb196 100644 --- a/openquake/calculators/event_based_risk.py +++ b/openquake/calculators/event_based_risk.py @@ -380,8 +380,6 @@ def pre_execute(self): self.events_per_sid = numpy.zeros(self.N, U32) self.datastore.swmr_on() set_oqparam(oq, self.assetcol, self.datastore) - ct = oq.concurrent_tasks or 1 - oq.maxweight = int(oq.ebrisk_maxsize / ct) self.A = A = len(self.assetcol) self.L = L = len(oq.loss_types) ELT = len(oq.ext_loss_types) diff --git a/openquake/commonlib/oqvalidation.py b/openquake/commonlib/oqvalidation.py index 6ce94fe466f4..7d79fd6b7de3 100644 --- a/openquake/commonlib/oqvalidation.py +++ b/openquake/commonlib/oqvalidation.py @@ -263,9 +263,6 @@ Example: *distance_bin_width = 20*. Default: no default -ebrisk_maxsize: - INTERNAL - epsilon_star: A boolean controlling the typology of disaggregation output to be provided. When True disaggregation is perfomed in terms of epsilon* rather then @@ -1149,7 +1146,6 @@ class OqParam(valid.ParamSet): split_sources = valid.Param(valid.boolean, True) split_by_gsim = valid.Param(valid.positiveint, 0) outs_per_task = valid.Param(valid.positiveint, 4) - ebrisk_maxsize = valid.Param(valid.positivefloat, 2E10) # used in ebrisk tectonic_region_type = valid.Param(valid.utf8, '*') time_event = valid.Param( valid.Choice('avg', 'day', 'night', 'transit'), 'avg') @@ -2234,8 +2230,9 @@ def to_ini(self): dic = {k: v for k, v in vars(self).items() if not k.startswith('_')} del dic['base_path'] del dic['req_site_params'] - dic.pop('export_dir', None) - dic.pop('all_cost_types', None) + for k in 'export_dir all_cost_types hdf5path ideduc M K A'.split(): + dic.pop(k, None) + if 'secondary_perils' in dic: dic['secondary_perils'] = ' '.join(dic['secondary_perils']) if 'aggregate_by' in dic: diff --git a/openquake/hazardlib/contexts.py b/openquake/hazardlib/contexts.py index bd8796a58a20..a806d5a32498 100644 --- a/openquake/hazardlib/contexts.py +++ b/openquake/hazardlib/contexts.py @@ -198,6 +198,10 @@ class Oq(object): def __init__(self, **hparams): vars(self).update(hparams) + @property + def min_iml(self): + return numpy.array([1E-10 for imt in self.imtls]) + def get_reqv(self): if 'reqv' not in self.inputs: return @@ -533,7 +537,6 @@ def __init__(self, trt, gsims, oq, monitor=Monitor(), extraparams=()): else: # OqParam param = vars(oq) param['split_sources'] = oq.split_sources - param['min_iml'] = oq.min_iml param['reqv'] = oq.get_reqv() param['af'] = getattr(oq, 'af', None) self.cross_correl = oq.cross_correl @@ -617,10 +620,7 @@ def _init2(self, param, extraparams): reqset.add('ch_phiss03') reqset.add('ch_phiss06') setattr(self, 'REQUIRES_' + req, reqset) - try: - self.min_iml = param['min_iml'] - except KeyError: - self.min_iml = numpy.array([0. for imt in self.imtls]) + self.min_iml = self.oq.min_iml self.reqv = param.get('reqv') if self.reqv is not None: self.REQUIRES_DISTANCES.add('repi') From e82fc05469821dce4e4dd01ebca36ea0c01fbb2c Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Thu, 17 Oct 2024 08:35:45 +0200 Subject: [PATCH 05/15] Cleanup --- openquake/commonlib/oqvalidation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openquake/commonlib/oqvalidation.py b/openquake/commonlib/oqvalidation.py index 7d79fd6b7de3..13aad4ea30a5 100644 --- a/openquake/commonlib/oqvalidation.py +++ b/openquake/commonlib/oqvalidation.py @@ -2230,7 +2230,7 @@ def to_ini(self): dic = {k: v for k, v in vars(self).items() if not k.startswith('_')} del dic['base_path'] del dic['req_site_params'] - for k in 'export_dir all_cost_types hdf5path ideduc M K A'.split(): + for k in 'export_dir exports all_cost_types hdf5path ideduc M K A'.split(): dic.pop(k, None) if 'secondary_perils' in dic: From 54e77aba9a8e6b9d27109248e5c8469a4acbf1a5 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Thu, 17 Oct 2024 09:12:27 +0200 Subject: [PATCH 06/15] Exporting rupture.csv --- openquake/calculators/export/risk.py | 5 ++++- openquake/calculators/extract.py | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index 53b7b9ece833..30631299246c 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -772,7 +772,10 @@ def export_job_zip(ekey, dstore): with open(job_ini, 'w') as out: out.write(oq.to_ini()) fnames = [job_ini] - # fnames.extend(export(('ruptures', 'csv'), dstore)) TODO + csv = extract(dstore, 'ruptures?slice=0&slice=1').array + with open('rupture.csv', 'w') as out: + out.write(csv) + fnames.append('rupture.csv') gsim_lt = dstore['full_lt'].gsim_lt dest = dstore.export_path('gsim_logic_tree.xml') with open(dest, 'wb') as out: diff --git a/openquake/calculators/extract.py b/openquake/calculators/extract.py index 70819ada5070..bddc91044c84 100644 --- a/openquake/calculators/extract.py +++ b/openquake/calculators/extract.py @@ -1504,8 +1504,13 @@ def extract_ruptures(dstore, what): for rgetter in getters.get_rupture_getters(dstore, rupids=rup_ids): ebrups.extend(rupture.get_ebr(proxy.rec, proxy.geom, rgetter.trt) for proxy in rgetter.get_proxies(min_mag)) + if 'slice' in qdict: + s0, s1 = qdict['slice'] + slc = slice(s0, s1) + else: + slc = slice(None) bio = io.StringIO() - arr = rupture.to_csv_array(ebrups) + arr = rupture.to_csv_array(ebrups[slc]) writers.write_csv(bio, arr, comment=comment) return bio.getvalue() From 23e9887405eadb4bb75e93df417e0cb1fd571416 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Thu, 17 Oct 2024 11:28:27 +0200 Subject: [PATCH 07/15] Added check_export_job --- openquake/calculators/export/risk.py | 19 +++++++++++-------- openquake/engine/tests/aristotle_test.py | 21 ++++++++++++++++++++- openquake/hazardlib/contexts.py | 7 +++++-- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index 30631299246c..f451319f42b4 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -15,6 +15,8 @@ # # You should have received a copy of the GNU Affero General Public License # along with OpenQuake. If not, see . + +import os import json import itertools import collections @@ -743,12 +745,11 @@ def convert_df_to_vulnerability(loss_type, df): return root -@export.add(('vulnerability', 'xml')) def export_vulnerability_xml(ekey, dstore): fnames = [] for loss_type, df in dstore.read_df('crm').groupby('loss_type'): nodeobj = convert_df_to_vulnerability(loss_type, df) - dest = dstore.export_path('%s_%s.%s' % ((loss_type,) + ekey)) + dest = os.path.join(dstore.export_dir, '%s_%s.%s' % ((loss_type,) + ekey)) with open(dest, 'wb') as out: nrml.write([nodeobj], out) fnames.append(dest) @@ -767,23 +768,25 @@ def export_job_zip(ekey, dstore): - vulnerability functions.xml - taxonomy_mapping.csv """ + edir = dstore.export_dir oq = dstore['oqparam'] - job_ini = dstore.export_path('job.ini') + job_ini = os.path.join(edir, 'job.ini') with open(job_ini, 'w') as out: out.write(oq.to_ini()) fnames = [job_ini] csv = extract(dstore, 'ruptures?slice=0&slice=1').array - with open('rupture.csv', 'w') as out: + dest = os.path.join(edir, 'rupture.csv') + with open(dest, 'w') as out: out.write(csv) - fnames.append('rupture.csv') + fnames.append(dest) gsim_lt = dstore['full_lt'].gsim_lt - dest = dstore.export_path('gsim_logic_tree.xml') + dest = os.path.join(edir, 'gsim_logic_tree.xml') with open(dest, 'wb') as out: nrml.write([gsim_lt.to_node()], out) fnames.append(dest) - fnames.extend(export(('vulnerability', 'xml'), dstore)) + fnames.extend(export_vulnerability_xml(('vulnerability', 'xml'), dstore)) - dest = dstore.export_path('taxonomy_mapping.csv') + dest = os.path.join(edir, 'taxonomy_mapping.csv') taxmap = dstore.read_df('taxmap') writer = writers.CsvWriter(fmt=writers.FIVEDIGITS) del taxmap['taxi'] diff --git a/openquake/engine/tests/aristotle_test.py b/openquake/engine/tests/aristotle_test.py index 12f353dbc236..5307eeb56e19 100644 --- a/openquake/engine/tests/aristotle_test.py +++ b/openquake/engine/tests/aristotle_test.py @@ -21,12 +21,31 @@ import unittest import pytest from openquake.calculators.checkers import check +from openquake.calculators.export import export + cd = pathlib.Path(__file__).parent +def check_export_job(dstore): + fnames = export(('job', 'zip'), dstore) + fnames = [os.path.basename(f) for f in fnames] + assert fnames == ['job.ini', + 'rupture.csv', + 'gsim_logic_tree.xml', + 'area_vulnerability.xml', + 'contents_vulnerability.xml', + 'nonstructural_vulnerability.xml', + 'number_vulnerability.xml', + 'occupants_vulnerability.xml', + 'residents_vulnerability.xml', + 'structural_vulnerability.xml', + 'taxonomy_mapping.csv'] + @pytest.mark.parametrize('n', [1, 2, 3]) def test_aristotle(n): if not os.path.exists(cd.parent.parent.parent / 'exposure.hdf5'): raise unittest.SkipTest('Please download exposure.hdf5') - check(cd / f'aristotle{n}/job.ini', what='aggrisk') + calc = check(cd / f'aristotle{n}/job.ini', what='aggrisk') + if n == 1: + check_export_job(calc.datastore) diff --git a/openquake/hazardlib/contexts.py b/openquake/hazardlib/contexts.py index a806d5a32498..ba233d4929df 100644 --- a/openquake/hazardlib/contexts.py +++ b/openquake/hazardlib/contexts.py @@ -193,7 +193,11 @@ def trivial(ctx, name): class Oq(object): + """ + A mock for OqParam + """ mea_tau_phi = False + split_sources = True def __init__(self, **hparams): vars(self).update(hparams) @@ -536,7 +540,6 @@ def __init__(self, trt, gsims, oq, monitor=Monitor(), extraparams=()): self.cross_correl = param.get('cross_correl') # cond_spectra_test else: # OqParam param = vars(oq) - param['split_sources'] = oq.split_sources param['reqv'] = oq.get_reqv() param['af'] = getattr(oq, 'af', None) self.cross_correl = oq.cross_correl @@ -590,7 +593,7 @@ def _init1(self, param): self.num_epsilon_bins = param.get('num_epsilon_bins', 1) self.disagg_bin_edges = param.get('disagg_bin_edges', {}) self.ps_grid_spacing = param.get('ps_grid_spacing') - self.split_sources = param.get('split_sources') + self.split_sources = self.oq.split_sources def _init2(self, param, extraparams): for gsim in self.gsims: From 0fe3e02b7d7f7c2d5c862ffe2b789ab3acba3d0a Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 05:57:53 +0200 Subject: [PATCH 08/15] Working on export_assetcol_csv --- openquake/calculators/export/risk.py | 33 +++++++++++++++++++++++----- openquake/server/views.py | 2 +- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index f451319f42b4..5a77946c99c2 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -657,8 +657,6 @@ def export_aggcurves_csv(ekey, dstore): return fnames -# TODO: rename to exposure and export both exposure.csv and exposure.xml -@export.add(('assetcol', 'csv')) def export_assetcol_csv(ekey, dstore): """ :param ekey: export key, i.e. a pair (datastore key, fmt) @@ -679,10 +677,35 @@ def export_assetcol_csv(ekey, dstore): df.loc[asset_idx, tagname] = tag_str df.drop(columns=['ordinal', 'site_id'], inplace=True) df['id'] = df['id'].apply(lambda x: x.decode('utf8')) - dest = dstore.export_path('%s.%s' % ekey) + dest_csv = dstore.export_path('%s.%s' % ekey) md = dstore.metadata - writer.save(df, dest, comment=md) - return [dest] + writer.save(df, dest_csv, comment=md) + + expo = dstore['exposure'][:] # cost_type, area_type, unit + N = node.Node + root = N('exposureModel', {'id': 'exposure', 'category': 'buildings'}) + root.append(N('description', {}, 'Generated exposure')) + conversions = N('conversions', {}) + costtypes = N('costTypes', {}) + for row in expo: + costtypes.nodes.append(N('costType', { + 'name': expo['cost'], + 'type': expo['per_area'], + 'unit': expo['unit']})) + conversions.append(N('area', {'type': 'per_asset', 'unit': 'SQM'})) + conversions.append(costtypes) + expfields = N('exposureFields', {}) + for f in fs: + expfields.append(N('field', {'input': inp, 'oq': name})) + root.append(conversions) + root.append(expfields) + root.append(N('occupancyPeriods'), {}, 'night') + root.append(N('tagNames'), {}, tagnames) + root.append(N('assets', {}, os.path.basename(dest_csv))) + dest_xml = dstore.export_path('%s.xml' % ekey[0]) + with open(dest_xml, 'wb') as out: + nrml.write([root], out) + return [dest_xml, dest_csv] @export.add(('reinsurance-risk_by_event', 'csv'), diff --git a/openquake/server/views.py b/openquake/server/views.py index 715fd4797b6e..c96099cf71bb 100644 --- a/openquake/server/views.py +++ b/openquake/server/views.py @@ -165,7 +165,7 @@ 'maximum_distance_stations': 'float ≥ 0', } -HIDDEN_OUTPUTS = ['assetcol'] +HIDDEN_OUTPUTS = ['job'] # disable check on the export_dir, since the WebUI exports in a tmpdir oqvalidation.OqParam.is_valid_export_dir = lambda self: True From 457b3482e6dccee29147d1ad56d738360f08f226 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 08:03:31 +0200 Subject: [PATCH 09/15] Fixed tests --- openquake/calculators/export/__init__.py | 3 +- openquake/calculators/export/risk.py | 105 +++++++++--------- .../event_based_risk/case_1/job.ini | 65 +++++------ .../qa_tests_data/scenario/case_16/job.ini | 7 +- openquake/risklib/asset.py | 15 ++- 5 files changed, 96 insertions(+), 99 deletions(-) diff --git a/openquake/calculators/export/__init__.py b/openquake/calculators/export/__init__.py index 40ecbceae27e..b0a75fa00531 100644 --- a/openquake/calculators/export/__init__.py +++ b/openquake/calculators/export/__init__.py @@ -25,8 +25,9 @@ 'asce07': 'ASCE 7 Parameters', 'asce41': 'ASCE 41 Parameters', 'mag_dst_eps_sig': "Deterministic Earthquake Scenarios", - 'assetcol': 'Exposure', + 'job': 'job.zip', 'asset_risk': 'Exposure + Risk', + 'assetcol': 'Exposure CSV', 'gmf_data': 'Ground Motion Fields', 'damages-rlzs': 'Asset Risk Distributions', 'damages-stats': 'Asset Risk Statistics', diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index 5a77946c99c2..bf5b11fa98a6 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -17,6 +17,7 @@ # along with OpenQuake. If not, see . import os +import re import json import itertools import collections @@ -657,57 +658,6 @@ def export_aggcurves_csv(ekey, dstore): return fnames -def export_assetcol_csv(ekey, dstore): - """ - :param ekey: export key, i.e. a pair (datastore key, fmt) - :param dstore: datastore object - """ - assetcol = dstore['assetcol'].array - writer = writers.CsvWriter(fmt=writers.FIVEDIGITS) - df = pandas.DataFrame(assetcol) - tagcol = dstore['assetcol'].tagcol - tagnames = tagcol.tagnames - sorted_cols = sorted([col for col in tagnames if col in df.columns]) - unsorted_cols = [col for col in df.columns if col not in tagnames] - df = df[unsorted_cols + sorted_cols] - for asset_idx in range(len(assetcol)): - for tagname in tagnames: - tag_id = df[tagname][asset_idx] - tag_str = tagcol.get_tag(tagname, tag_id).split('=')[1] - df.loc[asset_idx, tagname] = tag_str - df.drop(columns=['ordinal', 'site_id'], inplace=True) - df['id'] = df['id'].apply(lambda x: x.decode('utf8')) - dest_csv = dstore.export_path('%s.%s' % ekey) - md = dstore.metadata - writer.save(df, dest_csv, comment=md) - - expo = dstore['exposure'][:] # cost_type, area_type, unit - N = node.Node - root = N('exposureModel', {'id': 'exposure', 'category': 'buildings'}) - root.append(N('description', {}, 'Generated exposure')) - conversions = N('conversions', {}) - costtypes = N('costTypes', {}) - for row in expo: - costtypes.nodes.append(N('costType', { - 'name': expo['cost'], - 'type': expo['per_area'], - 'unit': expo['unit']})) - conversions.append(N('area', {'type': 'per_asset', 'unit': 'SQM'})) - conversions.append(costtypes) - expfields = N('exposureFields', {}) - for f in fs: - expfields.append(N('field', {'input': inp, 'oq': name})) - root.append(conversions) - root.append(expfields) - root.append(N('occupancyPeriods'), {}, 'night') - root.append(N('tagNames'), {}, tagnames) - root.append(N('assets', {}, os.path.basename(dest_csv))) - dest_xml = dstore.export_path('%s.xml' % ekey[0]) - with open(dest_xml, 'wb') as out: - nrml.write([root], out) - return [dest_xml, dest_csv] - - @export.add(('reinsurance-risk_by_event', 'csv'), ('reinsurance-aggcurves', 'csv'), ('reinsurance-avg_portfolio', 'csv'), @@ -779,6 +729,58 @@ def export_vulnerability_xml(ekey, dstore): return fnames + +@export.add(('assetcol', 'csv')) +def export_assetcol_csv(ekey, dstore): + assetcol = dstore['assetcol'].array + writer = writers.CsvWriter(fmt=writers.FIVEDIGITS) + df = pandas.DataFrame(assetcol) + tagcol = dstore['assetcol'].tagcol + tagnames = tagcol.tagnames + sorted_cols = sorted([col for col in tagnames if col in df.columns]) + unsorted_cols = [col for col in df.columns if col not in tagnames] + df = df[unsorted_cols + sorted_cols] + for asset_idx in range(len(assetcol)): + for tagname in tagnames: + tag_id = df[tagname][asset_idx] + tag_str = tagcol.get_tag(tagname, tag_id).split('=')[1] + df.loc[asset_idx, tagname] = tag_str + df.drop(columns=['ordinal', 'site_id'], inplace=True) + df['id'] = df['id'].apply(lambda x: x.decode('utf8')) + dest_csv = dstore.export_path('%s.%s' % ekey) + writer.save(df, dest_csv) + return [dest_csv] + + +def export_exposure(dstore): + """ + :param dstore: datastore object + """ + [dest_csv] = export(('assetcol', 'csv'), dstore) + dest_csv = re.sub(r'_\d+', '', os.path.basename(dest_csv)) + tagnames = dstore['assetcol/tagcol'].tagnames + cost_types = dstore.getitem('exposure') # cost_type, area_type, unit + N = node.Node + root = N('exposureModel', {'id': 'exposure', 'category': 'buildings'}) + root.append(N('description', {}, 'Generated exposure')) + conversions = N('conversions', {}) + costtypes = N('costTypes', {}) + for ct in cost_types: + costtypes.append(N('costType', { + 'name': ct['loss_type'], + 'type': ct['cost_type'], + 'unit': ct['unit']})) + conversions.append(costtypes) + root.append(conversions) + root.append(N('occupancyPeriods'), {}, 'night') + root.append(N('tagNames'), {}, tagnames) + root.append(N('assets', {}, os.path.basename(dest_csv))) + dest_xml = os.path.join(dstore.export_dir, 'exposure.xml') + with open(dest_xml, 'wb') as out: + nrml.write([root], out) + return [dest_xml, dest_csv] + + @export.add(('job', 'zip')) def export_job_zip(ekey, dstore): """ @@ -807,6 +809,7 @@ def export_job_zip(ekey, dstore): with open(dest, 'wb') as out: nrml.write([gsim_lt.to_node()], out) fnames.append(dest) + fnames.extend(export_exposure(dstore)) fnames.extend(export_vulnerability_xml(('vulnerability', 'xml'), dstore)) dest = os.path.join(edir, 'taxonomy_mapping.csv') diff --git a/openquake/qa_tests_data/event_based_risk/case_1/job.ini b/openquake/qa_tests_data/event_based_risk/case_1/job.ini index 31c0c63abc30..2f7b4daa9933 100644 --- a/openquake/qa_tests_data/event_based_risk/case_1/job.ini +++ b/openquake/qa_tests_data/event_based_risk/case_1/job.ini @@ -1,55 +1,40 @@ [general] +source_model_logic_tree_file = source_model_logic_tree.xml +structural_vulnerability_file = vulnerability_model_stco.xml +nonstructural_vulnerability_file = vulnerability_model_nonstco.xml +exposure_file = exposure1.xml +gsim_logic_tree_file = gmpe_logic_tree.xml random_seed = 23 master_seed = 42 description = Event Based Risk QA Test 1 calculation_mode = event_based_risk -structural_vulnerability_file = vulnerability_model_stco.xml -nonstructural_vulnerability_file = vulnerability_model_nonstco.xml -exposure_file = exposure1.zip -discard_assets = true - -region = 81.1 26, 88 26, 88 30, 81.1 30 - -asset_hazard_distance = 20 - -conditional_loss_poes = 0.1 - +discard_assets = 1 +region = 81.1 26.0, 88.0 26.0, 88.0 30.0, 81.1 30.0 +asset_hazard_distance = {'default': 20} +conditional_loss_poes = [0.1] quantiles = 0.25 - -avg_losses = true - -sites = 81.2985 29.1098, 83.082298 27.9006, 85.747703 27.9015 - -[logic_tree] - +avg_losses = 1 +sites = 81.2985 29.1098, 83.0823 27.9006, 85.7477 27.9015 number_of_logic_tree_samples = 0 - -[erf] - -# km -rupture_mesh_spacing = 5 +rupture_mesh_spacing = 5.0 width_of_mfd_bin = 0.3 -# km -area_source_discretization = 10 - -[site_params] - +area_source_discretization = 10.0 reference_vs30_type = measured reference_vs30_value = 760.0 reference_depth_to_2pt5km_per_sec = 5.0 reference_depth_to_1pt0km_per_sec = 100.0 - -[calculation] -source_model_logic_tree_file = source_model_logic_tree.xml -gsim_logic_tree_file = gmpe_logic_tree.xml -# years investigation_time = 50.0 ses_per_logic_tree_path = 20 -truncation_level = 3 -# km -maximum_distance = 100.0 +truncation_level = 3.0 +maximum_distance = {'default': [[2.5, 100.0], [10.2, 100.0]]} return_periods = [30, 60, 120, 240, 480, 960] -individual_rlzs = true - -[output] -export_dir = /tmp +individual_rlzs = 1 +poes = 0.8111243971624382 0.5654017914929218 0.34075936979955623 0.18806365384936508 0.09892489427870943 0.0507502405998248 +minimum_asset_loss = {'nonstructural': 0, 'structural': 0} +collect_rlzs = 0 +complex_fault_mesh_spacing = 5.0 +risk_imtls = {'PGA': [0.02], 'SA(0.8)': [0.03], 'SA(1.0)': [0.1], 'SA(0.2)': [0.2], 'SA(0.5)': [0.03]} +sec_imts = [] +minimum_intensity = {'PGA': 0.02, 'SA(0.2)': 0.2, 'SA(0.8)': 0.03, 'SA(0.5)': 0.03, 'SA(1.0)': 0.1} +mags_by_trt = {'Active Shallow Crust': ['5.25', '5.55', '5.85', '6.15', '6.45', '6.75']} +ground_motion_fields = 0 \ No newline at end of file diff --git a/openquake/qa_tests_data/scenario/case_16/job.ini b/openquake/qa_tests_data/scenario/case_16/job.ini index b541bb05d683..08b2f67f6e20 100644 --- a/openquake/qa_tests_data/scenario/case_16/job.ini +++ b/openquake/qa_tests_data/scenario/case_16/job.ini @@ -1,5 +1,8 @@ [general] +exposure_file = Example_Exposure.xml description = Importing a flexible exposure calculation_mode = scenario -exposure_file = Example_Exposure.xml -intensity_measure_types = PGA +intensity_measure_types_and_levels = {'PGA': [0]} +minimum_asset_loss = {} +collect_rlzs = 0 +complex_fault_mesh_spacing = 5.0 \ No newline at end of file diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index cf036ee51327..93828ade811b 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -870,8 +870,10 @@ class Exposure(object): def __toh5__(self): cc = self.cost_calculator loss_types = sorted(cc.cost_types) - dt = numpy.dtype([('cost_type', hdf5.vstr), ('unit', hdf5.vstr)]) + dt = numpy.dtype([('loss_type', hdf5.vstr), ('cost_type', hdf5.vstr), + ('unit', hdf5.vstr)]) array = numpy.zeros(len(loss_types), dt) + array['loss_type'] = loss_types array['cost_type'] = [cc.cost_types[lt] for lt in loss_types] array['unit'] = [cc.units[lt] for lt in loss_types] attrs = dict( @@ -1087,8 +1089,8 @@ def _read_csv(self, errors=None): missing = expected_header - header - {'exposure'} if len(header) < len(fields): raise InvalidFile( - '%s: The header %s contains a duplicated field' % - (fname, header)) + '%s: expected %d fields in %s, got %d' % + (fname, len(fields), header, len(header))) elif missing: raise InvalidFile('%s: missing %s' % (fname, missing)) conv = {'lon': float, 'lat': float, 'number': float, 'area': float, @@ -1136,5 +1138,8 @@ def associate(self, haz_sitecol, haz_distance, region=None): haz_sitecol).assoc2(self, haz_distance, region, 'filter') def __repr__(self): - return '<%s with %s assets>' % (self.__class__.__name__, - len(self.assets)) + try: + num_assets = len(self.assets) + except AttributeError: + num_assets = '?' + return '<%s with %s assets>' % (self.__class__.__name__, num_assets) From cc83bdca1b2e3984dd798b9f5a0377d80b652dcf Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 08:10:51 +0200 Subject: [PATCH 10/15] Cleanup --- openquake/risklib/asset.py | 10 +++++++--- openquake/server/views.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index 93828ade811b..ffac6725523f 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -867,6 +867,10 @@ class Exposure(object): fields = ['occupancy_periods', 'assets', 'cost_calculator', 'tagcol', 'pairs'] + @property + def loss_types(self): + return sorted(self.cost_calculator.cost_types) + def __toh5__(self): cc = self.cost_calculator loss_types = sorted(cc.cost_types) @@ -877,7 +881,6 @@ def __toh5__(self): array['cost_type'] = [cc.cost_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), occupancy_periods=hdf5.array_of_vstr(self.occupancy_periods), pairs=self.pairs) return array, attrs @@ -885,7 +888,8 @@ def __toh5__(self): 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.cost_types = dict(zip(decode(array['loss_type']), + decode(array['cost_type']))) cc.units = dict(zip(self.loss_types, decode(array['unit']))) @staticmethod @@ -1033,7 +1037,7 @@ def build_mesh(self, assets_df): self.cost_calculator.update(array) self.mesh = mesh self.assets = array - self.loss_types = vfields + #self.loss_types = vfields self.occupancy_periods = ofields def _csv_header(self, value='value-', occupants='occupants_'): diff --git a/openquake/server/views.py b/openquake/server/views.py index c96099cf71bb..e3e0ba072655 100644 --- a/openquake/server/views.py +++ b/openquake/server/views.py @@ -165,7 +165,7 @@ 'maximum_distance_stations': 'float ≥ 0', } -HIDDEN_OUTPUTS = ['job'] +HIDDEN_OUTPUTS = ['assetcol', 'job'] # disable check on the export_dir, since the WebUI exports in a tmpdir oqvalidation.OqParam.is_valid_export_dir = lambda self: True From fce0121bbf4059c908d2152ffcee3564747e2f6c Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 08:32:32 +0200 Subject: [PATCH 11/15] Fixed reading exposure.hdf5 --- openquake/calculators/export/risk.py | 5 +++-- openquake/risklib/asset.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index bf5b11fa98a6..34a6fd17dd49 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -19,12 +19,13 @@ import os import re import json +import tempfile import itertools import collections import numpy import pandas -from openquake.baselib import hdf5, writers, general, node +from openquake.baselib import hdf5, writers, general, node, config from openquake.baselib.python3compat import decode from openquake.hazardlib import nrml from openquake.hazardlib.stats import compute_stats2 @@ -793,7 +794,7 @@ def export_job_zip(ekey, dstore): - vulnerability functions.xml - taxonomy_mapping.csv """ - edir = dstore.export_dir + edir = config.directory.custom_tmp or tempfile.gettempdir() oq = dstore['oqparam'] job_ini = os.path.join(edir, 'job.ini') with open(job_ini, 'w') as out: diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index ffac6725523f..f3a696f15a6c 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -888,8 +888,9 @@ def __toh5__(self): def __fromh5__(self, array, attrs): vars(self).update(attrs) cc = self.cost_calculator = object.__new__(CostCalculator) - cc.cost_types = dict(zip(decode(array['loss_type']), - decode(array['cost_type']))) + # in exposure.hdf5 `loss_types` is an attribute + loss_types = attrs.get('loss_types') or decode(array['loss_type']) + cc.cost_types = dict(zip(loss_types, decode(array['cost_type']))) cc.units = dict(zip(self.loss_types, decode(array['unit']))) @staticmethod From 4d9a3100fdd1e37db0524fde0243dec1ec44f40b Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 08:43:47 +0200 Subject: [PATCH 12/15] Fixed event_based_risk/case_1/job.ini --- openquake/qa_tests_data/event_based_risk/case_1/job.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openquake/qa_tests_data/event_based_risk/case_1/job.ini b/openquake/qa_tests_data/event_based_risk/case_1/job.ini index 2f7b4daa9933..fb80eef5c993 100644 --- a/openquake/qa_tests_data/event_based_risk/case_1/job.ini +++ b/openquake/qa_tests_data/event_based_risk/case_1/job.ini @@ -2,7 +2,7 @@ source_model_logic_tree_file = source_model_logic_tree.xml structural_vulnerability_file = vulnerability_model_stco.xml nonstructural_vulnerability_file = vulnerability_model_nonstco.xml -exposure_file = exposure1.xml +exposure_file = exposure1.zip gsim_logic_tree_file = gmpe_logic_tree.xml random_seed = 23 master_seed = 42 @@ -37,4 +37,4 @@ risk_imtls = {'PGA': [0.02], 'SA(0.8)': [0.03], 'SA(1.0)': [0.1], 'SA(0.2)': [0. sec_imts = [] minimum_intensity = {'PGA': 0.02, 'SA(0.2)': 0.2, 'SA(0.8)': 0.03, 'SA(0.5)': 0.03, 'SA(1.0)': 0.1} mags_by_trt = {'Active Shallow Crust': ['5.25', '5.55', '5.85', '6.15', '6.45', '6.75']} -ground_motion_fields = 0 \ No newline at end of file +ground_motion_fields = 0 From 676677c4ca2df5fabe95d036d78a2b0e337fbae6 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 08:52:09 +0200 Subject: [PATCH 13/15] Fix --- openquake/risklib/asset.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openquake/risklib/asset.py b/openquake/risklib/asset.py index f3a696f15a6c..82d852d30d92 100644 --- a/openquake/risklib/asset.py +++ b/openquake/risklib/asset.py @@ -889,7 +889,10 @@ def __fromh5__(self, array, attrs): vars(self).update(attrs) cc = self.cost_calculator = object.__new__(CostCalculator) # in exposure.hdf5 `loss_types` is an attribute - loss_types = attrs.get('loss_types') or decode(array['loss_type']) + loss_types = attrs.get('loss_types') + if loss_types is None: + # for engine version >= 3.22 + loss_types = decode(array['loss_type']) cc.cost_types = dict(zip(loss_types, decode(array['cost_type']))) cc.units = dict(zip(self.loss_types, decode(array['unit']))) From ea98f198575d993429241f13fcc621b886072237 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 09:10:57 +0200 Subject: [PATCH 14/15] Fixed test --- openquake/calculators/export/risk.py | 4 ++-- openquake/engine/tests/aristotle_test.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index 34a6fd17dd49..cad1f8045f3a 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -773,8 +773,8 @@ def export_exposure(dstore): 'unit': ct['unit']})) conversions.append(costtypes) root.append(conversions) - root.append(N('occupancyPeriods'), {}, 'night') - root.append(N('tagNames'), {}, tagnames) + root.append(N('occupancyPeriods', {}, 'night')) + root.append(N('tagNames', {}, tagnames)) root.append(N('assets', {}, os.path.basename(dest_csv))) dest_xml = os.path.join(dstore.export_dir, 'exposure.xml') with open(dest_xml, 'wb') as out: diff --git a/openquake/engine/tests/aristotle_test.py b/openquake/engine/tests/aristotle_test.py index 5307eeb56e19..71a7c818848c 100644 --- a/openquake/engine/tests/aristotle_test.py +++ b/openquake/engine/tests/aristotle_test.py @@ -32,6 +32,8 @@ def check_export_job(dstore): assert fnames == ['job.ini', 'rupture.csv', 'gsim_logic_tree.xml', + 'exposure.xml', + 'assetcol.csv', 'area_vulnerability.xml', 'contents_vulnerability.xml', 'nonstructural_vulnerability.xml', From f99bbe2d230c97ce039a2890742cc47520393c62 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Fri, 18 Oct 2024 10:30:42 +0200 Subject: [PATCH 15/15] More work on export_job_zip --- openquake/calculators/export/risk.py | 33 ++++++++++++------------ openquake/commonlib/oqvalidation.py | 3 ++- openquake/engine/tests/aristotle_test.py | 6 ++--- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/openquake/calculators/export/risk.py b/openquake/calculators/export/risk.py index cad1f8045f3a..4cefb7de4970 100644 --- a/openquake/calculators/export/risk.py +++ b/openquake/calculators/export/risk.py @@ -17,8 +17,8 @@ # along with OpenQuake. If not, see . import os -import re import json +import shutil import tempfile import itertools import collections @@ -719,11 +719,11 @@ def convert_df_to_vulnerability(loss_type, df): return root -def export_vulnerability_xml(ekey, dstore): +def export_vulnerability_xml(dstore, edir): fnames = [] for loss_type, df in dstore.read_df('crm').groupby('loss_type'): nodeobj = convert_df_to_vulnerability(loss_type, df) - dest = os.path.join(dstore.export_dir, '%s_%s.%s' % ((loss_type,) + ekey)) + dest = os.path.join(edir, '%s_vulnerability.xml' % loss_type) with open(dest, 'wb') as out: nrml.write([nodeobj], out) fnames.append(dest) @@ -753,12 +753,13 @@ def export_assetcol_csv(ekey, dstore): return [dest_csv] -def export_exposure(dstore): +def export_exposure(dstore, edir): """ :param dstore: datastore object """ - [dest_csv] = export(('assetcol', 'csv'), dstore) - dest_csv = re.sub(r'_\d+', '', os.path.basename(dest_csv)) + [dest] = export(('assetcol', 'csv'), dstore) + assetcol_csv = os.path.join(edir, 'assetcol.csv') + shutil.move(dest, assetcol_csv) tagnames = dstore['assetcol/tagcol'].tagnames cost_types = dstore.getitem('exposure') # cost_type, area_type, unit N = node.Node @@ -775,11 +776,11 @@ def export_exposure(dstore): root.append(conversions) root.append(N('occupancyPeriods', {}, 'night')) root.append(N('tagNames', {}, tagnames)) - root.append(N('assets', {}, os.path.basename(dest_csv))) - dest_xml = os.path.join(dstore.export_dir, 'exposure.xml') - with open(dest_xml, 'wb') as out: + root.append(N('assets', {}, 'assetcol.csv')) + exposure_xml = os.path.join(edir, 'exposure.xml') + with open(exposure_xml, 'wb') as out: nrml.write([root], out) - return [dest_xml, dest_csv] + return [exposure_xml, assetcol_csv] @export.add(('job', 'zip')) @@ -790,16 +791,17 @@ def export_job_zip(ekey, dstore): - rupture.csv - gsim_lt.xml - site_model.csv - - exposure.xml and exposure.csv + - exposure.xml and assetcol.csv - vulnerability functions.xml - taxonomy_mapping.csv """ - edir = config.directory.custom_tmp or tempfile.gettempdir() oq = dstore['oqparam'] + edir = tempfile.mkdtemp(dir=config.directory.custom_tmp or tempfile.gettempdir()) + fnames = export_exposure(dstore, edir) job_ini = os.path.join(edir, 'job.ini') with open(job_ini, 'w') as out: - out.write(oq.to_ini()) - fnames = [job_ini] + out.write(oq.to_ini(exposure='exposure.xml')) + fnames.append(job_ini) csv = extract(dstore, 'ruptures?slice=0&slice=1').array dest = os.path.join(edir, 'rupture.csv') with open(dest, 'w') as out: @@ -810,8 +812,7 @@ def export_job_zip(ekey, dstore): with open(dest, 'wb') as out: nrml.write([gsim_lt.to_node()], out) fnames.append(dest) - fnames.extend(export_exposure(dstore)) - fnames.extend(export_vulnerability_xml(('vulnerability', 'xml'), dstore)) + fnames.extend(export_vulnerability_xml(dstore, edir)) dest = os.path.join(edir, 'taxonomy_mapping.csv') taxmap = dstore.read_df('taxmap') diff --git a/openquake/commonlib/oqvalidation.py b/openquake/commonlib/oqvalidation.py index d7d462ffeb57..6611ef54200a 100644 --- a/openquake/commonlib/oqvalidation.py +++ b/openquake/commonlib/oqvalidation.py @@ -2228,11 +2228,12 @@ def docs(cls): return dic # tested in run-demos.sh - def to_ini(self): + def to_ini(self, **inputs): """ Converts the parameters into a string in .ini format """ dic = {k: v for k, v in vars(self).items() if not k.startswith('_')} + dic['inputs'].update(inputs) del dic['base_path'] del dic['req_site_params'] for k in 'export_dir exports all_cost_types hdf5path ideduc M K A'.split(): diff --git a/openquake/engine/tests/aristotle_test.py b/openquake/engine/tests/aristotle_test.py index 71a7c818848c..ff653126191c 100644 --- a/openquake/engine/tests/aristotle_test.py +++ b/openquake/engine/tests/aristotle_test.py @@ -29,11 +29,11 @@ def check_export_job(dstore): fnames = export(('job', 'zip'), dstore) fnames = [os.path.basename(f) for f in fnames] - assert fnames == ['job.ini', + assert fnames == ['exposure.xml', + 'assetcol.csv', + 'job.ini', 'rupture.csv', 'gsim_logic_tree.xml', - 'exposure.xml', - 'assetcol.csv', 'area_vulnerability.xml', 'contents_vulnerability.xml', 'nonstructural_vulnerability.xml',