Skip to content

Commit 7b6471c

Browse files
authored
Merge pull request #10079 from gem/damages
Changed the damage outputs
2 parents 01256c1 + e15c8c2 commit 7b6471c

File tree

60 files changed

+890
-913
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+890
-913
lines changed

debian/changelog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[Michele Simionato]
2+
* Specifying total_losses is now mandatory in damage calculations with
3+
multiple loss types
24
* Added support for consequence=losses for liquefaction and landslides
35
* Added a check for missing secondary perils
46
* Added loss types liquefaction and landslide

openquake/calculators/classical_damage.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,21 @@ def classical_damage(riskinputs, param, monitor):
3939
dictionaries asset_ordinal -> damage(R, L, D)
4040
"""
4141
crmodel = monitor.read('crmodel')
42+
total_loss_types = crmodel.oqparam.total_loss_types
4243
mon = monitor('getting hazard', measuremem=False)
4344
for ri in riskinputs:
4445
R = ri.hazard_getter.R
45-
L = len(crmodel.lti)
4646
D = len(crmodel.damage_states)
47-
result = AccumDict(accum=numpy.zeros((R, L, D), F32))
47+
result = AccumDict(accum=numpy.zeros((R, D), F32))
4848
with mon:
4949
haz = ri.hazard_getter.get_hazard()
5050
for taxo, assets in ri.asset_df.groupby('taxonomy'):
5151
for rlz in range(R):
5252
hcurve = haz[:, rlz]
5353
out = crmodel.get_output(assets, hcurve)
54-
for li, loss_type in enumerate(crmodel.loss_types):
54+
for loss_type in total_loss_types:
5555
for a, frac in zip(assets.ordinal, out[loss_type]):
56-
result[a][rlz, li] = frac
56+
result[a][rlz] += frac
5757
yield result
5858

5959

@@ -70,16 +70,15 @@ def post_execute(self, result):
7070
Export the result in CSV format.
7171
7272
:param result:
73-
a dictionary asset_ordinal -> array(R, L, D)
73+
a dictionary asset_ordinal -> array(R, D)
7474
"""
7575
D = len(self.crmodel.damage_states)
76-
damages = numpy.zeros((self.A, self.R, self.L, D), numpy.float32)
76+
damages = numpy.zeros((self.A, self.R, D), numpy.float32)
7777
for a in result:
7878
damages[a] = result[a]
7979
self.datastore['damages-rlzs'] = damages
8080
stats.set_rlzs_stats(self.datastore, 'damages-rlzs',
8181
assets=self.assetcol['id'],
82-
loss_type=self.oqparam.loss_types,
8382
dmg_state=self.crmodel.damage_states)
8483
dmg = views.view('portfolio_damage', self.datastore)
8584
logging.info('\n' + views.text_table(dmg, ext='org'))

openquake/calculators/event_based_damage.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,11 @@ class Dparam:
5353

5454
def zero_dmgcsq(A, R, crmodel):
5555
"""
56-
:returns: an array of zeros of shape (A, R, L, Dc)
56+
:returns: an array of zeros of shape (A, R, Dc)
5757
"""
5858
dmg_csq = crmodel.get_dmg_csq()
59-
L = len(crmodel.loss_types)
6059
Dc = len(dmg_csq) + 1 # damages + consequences
61-
return numpy.zeros((A, R, L, Dc), F32)
60+
return numpy.zeros((A, R, Dc), F32)
6261

6362

6463
def damage_from_gmfs(gmfslices, oqparam, dstore, monitor):
@@ -152,15 +151,14 @@ def event_based_damage(df, oq, dstore, monitor):
152151
dmg_csq = crmodel.get_dmg_csq()
153152
csqidx = {dc: i + 1 for i, dc in enumerate(dmg_csq)}
154153
dmgcsq = zero_dmgcsq(len(assetcol), oq.R, crmodel)
155-
_A, R, L, Dc = dmgcsq.shape
154+
_A, R, Dc = dmgcsq.shape
156155
D = Dc - len(crmodel.get_consequences())
157156
if R > 1:
158157
allrlzs = dstore['events']['rlz_id']
159158
else:
160159
allrlzs = U32([0])
161-
assert len(oq.loss_types) == L
162160
with mon_risk:
163-
dddict = general.AccumDict(accum=numpy.zeros((L, Dc), F32)) # eid, kid
161+
dddict = general.AccumDict(accum=numpy.zeros(Dc, F32)) # eid, kid
164162
for sid, asset_df in assetcol.to_dframe().groupby('site_id'):
165163
# working one site at the time
166164
gmf_df = df[df.sid == sid]
@@ -181,17 +179,17 @@ def event_based_damage(df, oq, dstore, monitor):
181179
for aids, d4 in _gen_d4(asset_df, gmf_df, crmodel, dparam):
182180
for lti, d3 in enumerate(d4):
183181
if R == 1:
184-
dmgcsq[aids, 0, lti] += d3.sum(axis=1)
182+
dmgcsq[aids, 0] += d3.sum(axis=1)
185183
else:
186184
for e, rlz in enumerate(dparam.rlzs):
187-
dmgcsq[aids, rlz, lti] += d3[:, e]
185+
dmgcsq[aids, rlz] += d3[:, e]
188186
tot = d3.sum(axis=0) # sum on the assets
189187
for e, eid in enumerate(eids):
190-
dddict[eid, oq.K][lti] += tot[e]
188+
dddict[eid, oq.K] += tot[e]
191189
if oq.K:
192190
for kids in dparam.aggids:
193191
for a, aid in enumerate(aids):
194-
dddict[eid, kids[aid]][lti] += d3[a, e]
192+
dddict[eid, kids[aid]] += d3[a, e]
195193

196194
return _dframe(dddict, csqidx, oq.loss_types), dmgcsq
197195

@@ -205,7 +203,7 @@ def _dframe(adic, csqidx, loss_types):
205203
dic['event_id'].append(eid)
206204
dic['loss_id'].append(scientific.LOSSID[lt])
207205
for cname, ci in csqidx.items():
208-
dic[cname].append(dd[li, ci])
206+
dic[cname].append(dd[ci])
209207
fix_dtypes(dic)
210208
return pandas.DataFrame(dic)
211209

@@ -281,7 +279,7 @@ def post_execute(self, dummy):
281279
"""
282280
oq = self.oqparam
283281
# no damage check, perhaps the sites where disjoint from gmf_data
284-
if self.dmgcsq[:, :, :, 1:].sum() == 0:
282+
if self.dmgcsq[:, :, 1:].sum() == 0:
285283
haz_sids = self.datastore['gmf_data/sid'][:]
286284
count = numpy.isin(haz_sids, self.sitecol.sids).sum()
287285
if count == 0:
@@ -299,22 +297,21 @@ def post_execute(self, dummy):
299297
with prc.datastore:
300298
prc.run(exports='')
301299

302-
_A, _R, L, _Dc = self.dmgcsq.shape
300+
_A, _R, _Dc = self.dmgcsq.shape
303301
D = len(self.crmodel.damage_states)
304302
# fix no_damage distribution for events with zero damage
305303
number = self.assetcol['value-number']
304+
nl = len(oq.total_loss_types)
306305
for r in range(self.R):
307306
ne = prc.num_events[r]
308-
for li in range(L):
309-
self.dmgcsq[:, r, li, 0] = ( # no damage
310-
number * ne - self.dmgcsq[:, r, li, 1:D].sum(axis=1))
307+
self.dmgcsq[:, r, 0] = ( # no damage
308+
nl * number * ne - self.dmgcsq[:, r, 1:D].sum(axis=1))
311309
self.dmgcsq[:, r] /= ne
312310
self.datastore['damages-rlzs'] = self.dmgcsq
313311
set_rlzs_stats(self.datastore,
314312
'damages-rlzs',
315313
asset_id=self.assetcol['id'],
316314
rlz=numpy.arange(self.R),
317-
loss_type=oq.loss_types,
318315
dmg_state=['no_damage'] + self.crmodel.get_dmg_csq())
319316

320317
if (hasattr(oq, 'infrastructure_connectivity_analysis')

openquake/calculators/export/risk.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -380,13 +380,10 @@ def export_loss_maps_npz(ekey, dstore):
380380

381381
def modal_damage_array(data, damage_dt):
382382
# determine the damage state with the highest probability
383-
A, _L, _D = data.shape
384-
dmgstate = damage_dt['structural'].names
385-
arr = numpy.zeros(A, [('modal-ds-' + lt, hdf5.vstr)
386-
for lt in damage_dt.names])
387-
for li, loss_type in enumerate(damage_dt.names):
388-
arr['modal-ds-' + loss_type] = [dmgstate[data[a, li].argmax()]
389-
for a in range(A)]
383+
A, _D = data.shape
384+
dmgstate = damage_dt.names
385+
arr = numpy.zeros(A, [('modal-ds', hdf5.vstr)])
386+
arr['modal-ds'] = [dmgstate[data[a].argmax()] for a in range(A)]
390387
return arr
391388

392389

@@ -397,7 +394,7 @@ def export_damages_csv(ekey, dstore):
397394
ebd = oq.calculation_mode == 'event_based_damage'
398395
dmg_dt = build_damage_dt(dstore)
399396
rlzs = dstore['full_lt'].get_realizations()
400-
orig = dstore[ekey[0]][:] # shape (A, R, L, D)
397+
orig = dstore[ekey[0]][:] # shape (A, R, D)
401398
writer = writers.CsvWriter(fmt='%.6E')
402399
assets = get_assets(dstore)
403400
md = dstore.metadata
@@ -418,14 +415,13 @@ def export_damages_csv(ekey, dstore):
418415
if ebd: # export only the consequences from damages-rlzs, i == 0
419416
rate = len(dstore['events']) * oq.time_ratio / len(rlzs)
420417
data = orig[:, i] * rate
421-
A, _L, Dc = data.shape
418+
A, Dc = data.shape
422419
if Dc == D: # no consequences, export nothing
423420
return []
424421
csq_dt = build_csq_dt(dstore)
425422
damages = numpy.zeros(A, csq_dt)
426423
for a in range(A):
427-
for li, lt in enumerate(csq_dt.names):
428-
damages[lt][a] = tuple(data[a, li, D:Dc])
424+
damages[a] = tuple(data[a, D:Dc])
429425
fname = dstore.build_fname('avg_risk', ros, ekey[1])
430426
else: # scenario_damage, classical_damage
431427
if oq.modal_damage_state:

openquake/calculators/extract.py

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -716,15 +716,6 @@ def _filter_agg(assetcol, losses, selected, stats=''):
716716
dict(selected=encode(selected), tags=encode(tags), stats=stats))
717717

718718

719-
def get_loss_type_tags(what):
720-
try:
721-
loss_type, query_string = what.rsplit('?', 1)
722-
except ValueError: # no question mark
723-
loss_type, query_string = what, ''
724-
tags = query_string.split('&') if query_string else []
725-
return loss_type, tags
726-
727-
728719
# probably not used
729720
@extract.add('csq_curves')
730721
def extract_csq_curves(dstore, what):
@@ -832,7 +823,11 @@ def extract_agg_losses(dstore, what):
832823
an array of shape (R,), being R the number of realizations
833824
an array of length 0 if there is no data for the given tags
834825
"""
835-
loss_type, tags = get_loss_type_tags(what)
826+
if '?' in what:
827+
loss_type, query_string = what.rsplit('?', 1)
828+
else:
829+
loss_type, query_string = what, ''
830+
tags = query_string.split('&') if query_string else []
836831
if not loss_type:
837832
raise ValueError('loss_type not passed in agg_losses/<loss_type>')
838833
if 'avg_losses-stats/' + loss_type in dstore:
@@ -850,18 +845,16 @@ def extract_agg_losses(dstore, what):
850845
def extract_agg_damages(dstore, what):
851846
"""
852847
Aggregate damages of the given loss type and tags. Use it as
853-
/extract/agg_damages/structural?taxonomy=RC&custom_site_id=20126
848+
/extract/agg_damages?taxonomy=RC&custom_site_id=20126
854849
855850
:returns:
856851
array of shape (R, D), being R the number of realizations and D the
857852
number of damage states, or an array of length 0 if there is no data
858853
for the given tags
859854
"""
860-
loss_type, tags = get_loss_type_tags(what)
855+
tags = what.split('&') if what else []
861856
if 'damages-rlzs' in dstore:
862-
oq = dstore['oqparam']
863-
lti = oq.lti[loss_type]
864-
damages = dstore['damages-rlzs'][:, :, lti]
857+
damages = dstore['damages-rlzs'][:, :]
865858
else:
866859
raise KeyError('No damages found in %s' % dstore)
867860
return _filter_agg(dstore['assetcol'], damages, tags)
@@ -1044,42 +1037,37 @@ def build_damage_dt(dstore):
10441037
:returns:
10451038
a composite dtype loss_type -> (ds1, ds2, ...)
10461039
"""
1047-
oq = dstore['oqparam']
10481040
attrs = json.loads(dstore.get_attr('damages-rlzs', 'json'))
10491041
limit_states = list(dstore.get_attr('crm', 'limit_states'))
10501042
csqs = attrs['dmg_state'][len(limit_states) + 1:] # consequences
10511043
dt_list = [(ds, F32) for ds in ['no_damage'] + limit_states + csqs]
10521044
damage_dt = numpy.dtype(dt_list)
1053-
loss_types = oq.loss_dt().names
1054-
return numpy.dtype([(lt, damage_dt) for lt in loss_types])
1045+
return damage_dt
10551046

10561047

10571048
def build_csq_dt(dstore):
10581049
"""
10591050
:param dstore: a datastore instance
10601051
:returns:
1061-
a composite dtype loss_type -> (csq1, csq2, ...)
1052+
a composite dtype (csq1, csq2, ...)
10621053
"""
1063-
oq = dstore['oqparam']
10641054
attrs = json.loads(dstore.get_attr('damages-rlzs', 'json'))
10651055
limit_states = list(dstore.get_attr('crm', 'limit_states'))
10661056
csqs = attrs['dmg_state'][len(limit_states) + 1:] # consequences
10671057
dt = numpy.dtype([(csq, F32) for csq in csqs])
1068-
loss_types = oq.loss_dt().names
1069-
return numpy.dtype([(lt, dt) for lt in loss_types])
1058+
return dt
10701059

10711060

10721061
def build_damage_array(data, damage_dt):
10731062
"""
1074-
:param data: an array of shape (A, L, D)
1075-
:param damage_dt: a damage composite data type loss_type -> states
1063+
:param data: an array of shape (A, D)
1064+
:param damage_dt: a damage data type
10761065
:returns: a composite array of length N and dtype damage_dt
10771066
"""
1078-
A, _L, _D = data.shape
1067+
A, _D = data.shape
10791068
dmg = numpy.zeros(A, damage_dt)
10801069
for a in range(A):
1081-
for li, lt in enumerate(damage_dt.names):
1082-
dmg[lt][a] = tuple(data[a, li])
1070+
dmg[a] = tuple(data[a])
10831071
return dmg
10841072

10851073

openquake/calculators/tests/scenario_damage_test.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,10 @@ def test_case_1(self):
5555
view('num_units', self.calc.datastore)
5656

5757
# test agg_damages, 1 realization x 3 damage states
58-
[dmg] = extract(self.calc.datastore, 'agg_damages/structural?'
59-
'taxonomy=RC&CRESTA=01.1')
58+
[dmg] = extract(self.calc.datastore, 'agg_damages?taxonomy=RC&CRESTA=01.1')
6059
aac([1482., 489., 29.], dmg, atol=1E-4)
6160
# test no intersection
62-
dmg = extract(self.calc.datastore, 'agg_damages/structural?'
63-
'taxonomy=RM&CRESTA=01.1')
61+
dmg = extract(self.calc.datastore, 'agg_damages?taxonomy=RM&CRESTA=01.1')
6462
self.assertEqual(dmg.shape, ())
6563

6664
# missing fragility functions
@@ -79,16 +77,15 @@ def test_case_1c(self):
7977
[fname] = export(('damages-rlzs', 'csv'), self.calc.datastore)
8078
self.assertEqualFiles('expected/' + strip_calc_id(fname), fname)
8179
df = self.calc.datastore.read_df('damages-rlzs', 'asset_id')
82-
self.assertEqual(list(df.columns),
83-
['rlz', 'loss_type', 'dmg_state', 'value'])
80+
self.assertEqual(list(df.columns), ['rlz', 'dmg_state', 'value'])
8481

8582
# check risk_by_event
8683
[fname] = export(('risk_by_event', 'csv'), self.calc.datastore)
8784
self.assertEqualFiles('expected/' + strip_calc_id(fname), fname,
8885
delta=1E-5)
8986

9087
# check agg_damages extraction
91-
total = extract(self.calc.datastore, 'agg_damages/structural')
88+
total = extract(self.calc.datastore, 'agg_damages')
9289

9390
aac(total, [[27652.219, 28132.8, 9511.933, 2870.9312, 11832.913]],
9491
atol=.1)
@@ -138,15 +135,15 @@ def test_case_5(self):
138135
def test_case_5a(self):
139136
# this is a case with two gsims and one asset
140137
self.assert_ok(case_5a, 'job_haz.ini,job_risk.ini')
141-
dmg = extract(self.calc.datastore, 'agg_damages/structural?taxonomy=*')
138+
dmg = extract(self.calc.datastore, 'agg_damages?taxonomy=*')
142139
self.assertEqual(dmg.array.shape, (1, 2, 5)) # (T, R, D)
143140
aac(dmg.array[0].sum(axis=0),
144141
[0.68951, 0.623331, 0.305033, 0.155678, 0.22645], atol=1E-5)
145142

146143
def test_case_6(self):
147144
# this is a case with 5 assets on the same point
148145
self.assert_ok(case_6, 'job_h.ini,job_r.ini')
149-
dmg = extract(self.calc.datastore, 'agg_damages/structural?taxonomy=*')
146+
dmg = extract(self.calc.datastore, 'agg_damages?taxonomy=*')
150147
tmpname = write_csv(None, dmg, fmt='%.5E') # (T, R, D) == (5, 1, 5)
151148
self.assertEqualFiles('expected/dmg_by_taxon.csv', tmpname,
152149
delta=1E-5)
@@ -165,7 +162,7 @@ def test_case_7(self):
165162
'risk_by_event', ['event_id', 'loss_id', 'agg_id'],
166163
dict(agg_id=K))
167164
self.assertEqual(len(df), 300)
168-
self.assertEqual(len(df[df.dmg_1 > 0]), 72) # only 72/300 are nonzero
165+
self.assertEqual(len(df[df.dmg_1 > 0]), 174) # only 174/300 are nonzero
169166

170167
def test_case_8(self):
171168
# case with a shakemap

openquake/calculators/views.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -560,9 +560,8 @@ def portfolio_dmgdist(token, dstore):
560560
oq = dstore['oqparam']
561561
dstates = ['no_damage'] + oq.limit_states
562562
D = len(dstates)
563-
arr = dstore['damages-rlzs'][:, 0, :, :D].sum(axis=0) # shape (L, D)
564-
tbl = numpy.zeros(len(arr), dt(['loss_type', 'total'] + dstates))
565-
tbl['loss_type'] = oq.loss_types
563+
arr = dstore['damages-rlzs'][:, 0, :D].sum(axis=0) # shape D
564+
tbl = numpy.zeros(len(arr), dt(['total'] + dstates))
566565
tbl['total'] = arr.sum(axis=1)
567566
for dsi, ds in enumerate(dstates):
568567
tbl[ds] = arr[:, dsi]
@@ -584,15 +583,14 @@ def view_portfolio_damage(token, dstore):
584583
del df['agg_id']
585584
del df['return_period']
586585
return df.set_index('loss_type')
587-
# dimensions assets, stat, loss_types, dmg_state
586+
# dimensions assets, stat, dmg_state
588587
if 'damages-stats' in dstore:
589588
attrs = get_shape_descr(dstore['damages-stats'].attrs['json'])
590589
arr = dstore.sel('damages-stats', stat='mean').sum(axis=(0, 1))
591590
else:
592591
attrs = get_shape_descr(dstore['damages-rlzs'].attrs['json'])
593-
arr = dstore.sel('damages-rlzs', rlz=0).sum(axis=(0, 1))
594-
rows = [(lt,) + tuple(row) for lt, row in zip(attrs['loss_type'], arr)]
595-
return numpy.array(rows, dt(['loss_type'] + list(attrs['dmg_state'])))
592+
arr = dstore.sel('damages-rlzs', rlz=0).sum(axis=(0, 1)) # shape D
593+
return numpy.array(arr, dt(list(attrs['dmg_state'])))
596594

597595

598596
def sum_table(records):
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
#,,,,,,,,"generated_by='OpenQuake engine 3.13.0-git63b4e49e3c', start_date='2022-01-04T10:03:02', checksum=2908959360, investigation_time=50.0, risk_investigation_time=100.0"
2-
asset_id,taxonomy,lon,lat,structural~no_damage,structural~slight,structural~moderate,structural~extensive,structural~complete
1+
#,,,,,,,,"generated_by='OpenQuake engine 3.22.0-gitcb9ea472a1', start_date='2024-10-23T02:12:41', checksum=2021864103, investigation_time=50.0, risk_investigation_time=100.0"
2+
asset_id,taxonomy,lon,lat,no_damage,slight,moderate,extensive,complete
33
a1,W,83.31382,29.46117,5.834455E-01,1.166002E-01,1.639836E-01,6.452727E-02,7.144331E-02

0 commit comments

Comments
 (0)