Skip to content

Commit f7f9475

Browse files
committed
improve performance of impact computation
1 parent 7f024d2 commit f7f9475

File tree

1 file changed

+54
-50
lines changed

1 file changed

+54
-50
lines changed

climada/engine/impact.py

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
LOGGER = logging.getLogger(__name__)
1616

17+
MAX_SIZE = 1.0e8
18+
""" Maximum matrix size for impact caluculation. If the matrix is bigger,
19+
it is chunked."""
20+
1721
class ImpactFreqCurve(object):
1822
""" Impact exceedence frequency curve.
1923
@@ -166,79 +170,79 @@ def calc(self, exposures, impact_funcs, hazard):
166170
# Get damage functions for this hazard
167171
haz_imp = impact_funcs.get_func(haz_type)
168172

173+
# Check if deductible and cover should be applied
174+
insure_flag = False
175+
if exposures.deductible.size and exposures.cover.size:
176+
insure_flag = True
177+
num_events = hazard.intensity.shape[0]
169178
# 3. Loop over exposures according to their impact function
170179
# Loop over impact functions
171180
for imp_fun in haz_imp:
181+
self.imp_fun = imp_fun
172182
# get indices of all the exposures with this impact function
173183
exp_iimp = np.where(exposures.impact_id[exp_idx] == imp_fun.id)[0]
184+
exp_step = int(MAX_SIZE/num_events)
185+
# separte in chunks if too many exposures
186+
i = -1
187+
for i in range(int(exp_iimp.size/exp_step)):
188+
self._exp_impact(exp_idx[exp_iimp[i*exp_step:(i+1)*exp_step]],\
189+
exposures, hazard, imp_fun, insure_flag)
190+
self._exp_impact(exp_idx[exp_iimp[(i+1)*exp_step:]],\
191+
exposures, hazard, imp_fun, insure_flag)
174192

175-
# loop over selected exposures
176-
for iexp in exp_idx[exp_iimp]:
177-
# compute impact on exposure
178-
event_row, impact = self._one_exposure(iexp, exposures, \
179-
hazard, imp_fun)
193+
self.aai_agg = sum(self.at_event * hazard.frequency)
180194

181-
# add values to impact impact
182-
self.at_event[event_row] += impact
183-
self.eai_exp[iexp] += np.squeeze(sum(impact * hazard. \
184-
frequency[event_row]))
185-
self.tot_value += exposures.value[iexp]
195+
def _exp_impact(self, exp_iimp, exposures, hazard, imp_fun, insure_flag):
196+
"""Compute impact for inpute exposure indexes and impact function.
186197
187-
self.aai_agg = sum(self.at_event * hazard.frequency)
198+
Parameters:
199+
exp_iimp (np.array): exposures indexes
200+
exposures (Exposures): exposures instance
201+
hazard (Hazard): hazard instance
202+
imp_fun (ImpactFunc): impact function instance
203+
insure_flag (bool): consider deductible and cover of exposures
204+
"""
205+
# get assigned centroids
206+
icens = exposures.assigned[hazard.tag.haz_type][exp_iimp]
207+
208+
# get affected intensities
209+
inten_val = hazard.intensity[:, icens].todense()
210+
# get affected fractions
211+
fract = hazard.fraction[:, icens]
212+
impact = fract.multiply(imp_fun.calc_mdr(inten_val)). \
213+
multiply(exposures.value[exp_iimp])
214+
215+
if insure_flag and impact.nonzero()[0].size:
216+
paa = np.interp(inten_val, imp_fun.intensity, imp_fun.paa)
217+
impact = np.minimum(np.maximum(impact - \
218+
exposures.deductible[exp_iimp] * paa, 0), \
219+
exposures.cover[exp_iimp])
220+
self.eai_exp[exp_iimp] += np.sum(np.asarray(impact) * \
221+
hazard.frequency.reshape(-1, 1), axis=0)
222+
else:
223+
self.eai_exp[exp_iimp] += np.squeeze(np.asarray(np.sum( \
224+
impact.multiply(hazard.frequency.reshape(-1, 1)), axis=0)))
225+
226+
self.at_event += np.squeeze(np.asarray(np.sum(impact, axis=1)))
227+
self.tot_value += np.sum(exposures.value[exp_iimp])
188228

189-
def plot_eai_exposure(self, ignore_null=True, **kwargs):
229+
def plot_eai_exposure(self, ignore_zero=True, **kwargs):
190230
"""Plot expected annual impact of each exposure.
191231
192232
Parameters:
193-
ignore_null (bool): ignore zero impact values at exposures
233+
ignore_zero (bool): ignore zero impact values at exposures
194234
kwargs (optional): arguments for hexbin matplotlib function
195235
196236
Returns:
197237
matplotlib.figure.Figure, cartopy.mpl.geoaxes.GeoAxesSubplot
198238
"""
199239
title = 'Expected annual impact'
200240
col_name = 'Impact ' + self.unit
201-
if ignore_null:
241+
if ignore_zero:
202242
pos_vals = self.eai_exp > 0
203243
else:
204244
pos_vals = np.ones((self.eai_exp.size,), dtype=bool)
205245
if 'reduce_C_function' not in kwargs:
206246
kwargs['reduce_C_function'] = np.sum
207247
return plot.geo_bin_from_array(self.eai_exp[pos_vals], \
208248
self.coord_exp[pos_vals], col_name, title, **kwargs)
209-
210-
@staticmethod
211-
def _one_exposure(iexp, exposures, hazard, imp_fun):
212-
"""Impact to one exposures.
213-
214-
Parameters:
215-
iexp (int): array index of the exposure computed
216-
exposures (Exposures): exposures
217-
hazard (Hazard): a hazard
218-
imp_fun (ImpactFunc): an impact function
219-
220-
Returns:
221-
event_row (np.array): hazard' events indices affecting exposure
222-
impact (np.array): impact for each event in event_row
223-
"""
224-
# get assigned centroid of this exposure
225-
icen = int(exposures.assigned[hazard.tag.haz_type][iexp])
226-
227-
# get intensities for this centroid
228-
event_row = hazard.intensity[:, icen].nonzero()[0]
229-
inten_val = np.asarray(hazard.intensity[event_row, icen].todense()). \
230-
squeeze()
231-
# get affected fraction for these events
232-
fract = np.squeeze(hazard.fraction[:, icen].toarray()[event_row])
233-
234-
# impact on this exposure
235-
impact = exposures.value[iexp] * imp_fun.calc_mdr(inten_val) * fract
236-
if np.count_nonzero(impact) > 0:
237-
# TODO: if needed?
238-
if (exposures.deductible[iexp] > 0) or \
239-
(exposures.cover[iexp] < exposures.value[iexp]):
240-
paa = np.interp(inten_val, imp_fun.intensity, imp_fun.paa)
241-
impact = np.minimum(np.maximum(impact - \
242-
exposures.deductible[iexp] * \
243-
paa, 0), exposures.cover[iexp])
244-
return event_row, impact

0 commit comments

Comments
 (0)