Skip to content

Commit 053ffc8

Browse files
committed
merge changes
2 parents 3d7e37a + 302be63 commit 053ffc8

File tree

9 files changed

+818
-178
lines changed

9 files changed

+818
-178
lines changed

climada/engine/impact_calc.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
from climada import CONFIG
3131
from climada.engine.impact import Impact
32+
from climada.engine.impact_forecast import ImpactForecast
33+
from climada.hazard.forecast import HazardForecast
3234

3335
LOGGER = logging.getLogger(__name__)
3436

@@ -217,7 +219,7 @@ def _return_impact(self, imp_mat_gen, save_mat):
217219
218220
Returns
219221
-------
220-
Impact
222+
Impact or ImpactForecast
221223
Impact Object initialize from the impact matrix
222224
223225
See Also
@@ -230,12 +232,31 @@ def _return_impact(self, imp_mat_gen, save_mat):
230232
at_event, eai_exp, aai_agg = self.risk_metrics(
231233
imp_mat, self.hazard.frequency
232234
)
235+
if isinstance(self.hazard, HazardForecast):
236+
eai_exp = np.full_like(eai_exp, np.nan, dtype=eai_exp.dtype)
237+
aai_agg = np.full_like(aai_agg, np.nan, dtype=aai_agg.dtype)
238+
LOGGER.warning(
239+
"eai_exp and aai_agg are undefined with forecasts. "
240+
"Setting them to NaN arrays."
241+
)
242+
233243
else:
244+
if isinstance(self.hazard, HazardForecast):
245+
raise ValueError(
246+
"Saving impact matrix is required when using HazardForecast."
247+
"Please set save_mat=True."
248+
)
234249
imp_mat = None
235250
at_event, eai_exp, aai_agg = self.stitch_risk_metrics(imp_mat_gen)
236-
return Impact.from_eih(
251+
252+
impact = Impact.from_eih(
237253
self.exposures, self.hazard, at_event, eai_exp, aai_agg, imp_mat
238254
)
255+
if isinstance(self.hazard, HazardForecast):
256+
return ImpactForecast.from_impact(
257+
impact, self.hazard.lead_time, self.hazard.member
258+
)
259+
return impact
239260

240261
def _return_empty(self, save_mat):
241262
"""
@@ -248,21 +269,37 @@ def _return_empty(self, save_mat):
248269
249270
Returns
250271
-------
251-
Impact
272+
Impact or ImpactForecast
252273
Empty impact object with correct array sizes.
253274
"""
254275
at_event = np.zeros(self.n_events)
255-
eai_exp = np.zeros(self.n_exp_pnt)
256-
aai_agg = 0.0
276+
if isinstance(self.hazard, HazardForecast):
277+
eai_exp = np.full(self.n_exp_pnt, np.nan)
278+
aai_agg = np.nan
279+
else:
280+
eai_exp = np.zeros(self.n_exp_pnt)
281+
aai_agg = 0.0
282+
257283
if save_mat:
258284
imp_mat = sparse.csr_matrix(
259285
(self.n_events, self.n_exp_pnt), dtype=np.float64
260286
)
261287
else:
288+
if isinstance(self.hazard, HazardForecast):
289+
raise ValueError(
290+
"Saving impact matrix is required when using HazardForecast. "
291+
"Please set save_mat=True."
292+
)
262293
imp_mat = None
263-
return Impact.from_eih(
294+
295+
impact = Impact.from_eih(
264296
self.exposures, self.hazard, at_event, eai_exp, aai_agg, imp_mat
265297
)
298+
if isinstance(self.hazard, HazardForecast):
299+
return ImpactForecast.from_impact(
300+
impact, self.hazard.lead_time, self.hazard.member
301+
)
302+
return impact
266303

267304
def minimal_exp_gdf(
268305
self, impf_col, assign_centroids, ignore_cover, ignore_deductible

climada/engine/impact_forecast.py

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import numpy as np
2525

2626
from ..util import log_level
27+
from ..util.checker import size
2728
from ..util.forecast import Forecast
2829
from .impact import Impact
2930

@@ -51,8 +52,8 @@ def __init__(
5152
impact_kwargs
5253
Keyword-arguments passed to ~:py:class`climada.engine.impact.Impact`.
5354
"""
54-
# TODO: Maybe assert array lengths?
5555
super().__init__(lead_time=lead_time, member=member, **impact_kwargs)
56+
self._check_sizes()
5657

5758
@classmethod
5859
def from_impact(
@@ -88,3 +89,154 @@ def from_impact(
8889
imp_mat=impact.imp_mat,
8990
haz_type=impact.haz_type,
9091
)
92+
93+
@property
94+
def at_event(self):
95+
"""Get the total impact for each member/lead_time combination."""
96+
LOGGER.warning(
97+
"at_event gives the total impact for one specific combination of member and "
98+
"lead_time."
99+
)
100+
return self._at_event
101+
102+
@at_event.setter
103+
def at_event(self, value):
104+
"""Set the total impact for each member/lead_time combination."""
105+
self._at_event = value
106+
107+
def local_exceedance_impact(
108+
self,
109+
return_periods=(25, 50, 100, 250),
110+
method="interpolate",
111+
min_impact=0,
112+
log_frequency=True,
113+
log_impact=True,
114+
bin_decimals=None,
115+
):
116+
"""Compution of local exceedance impact for given return periods is not
117+
implemented for ImpactForecast.
118+
119+
See Also
120+
--------
121+
See :py:meth:`~climada.engine.impact.Impact.local_exceedance_impact`
122+
123+
Raises
124+
------
125+
NotImplementedError
126+
"""
127+
128+
LOGGER.error("local_exceedance_impact is not defined for ImpactForecast")
129+
raise NotImplementedError(
130+
"local_exceedance_impact is not defined for ImpactForecast"
131+
)
132+
133+
def local_return_period(
134+
self,
135+
threshold_impact=(1000.0, 10000.0),
136+
method="interpolate",
137+
min_impact=0,
138+
log_frequency=True,
139+
log_impact=True,
140+
bin_decimals=None,
141+
):
142+
"""Compution of local return period for given impact thresholds is not
143+
implemented for ImpactForecast.
144+
145+
See Also
146+
--------
147+
See :py:meth:`~climada.engine.impact.Impact.local_return_period`
148+
149+
Raises
150+
-------
151+
NotImplementedError
152+
"""
153+
154+
LOGGER.error("local_return_period is not defined for ImpactForecast")
155+
raise NotImplementedError(
156+
"local_return_period is not defined for ImpactForecast"
157+
)
158+
159+
def calc_freq_curve(self, return_per=None):
160+
"""Computation of the impact exceedance frequency curve is not
161+
implemented for ImpactForecast.
162+
163+
See Also
164+
--------
165+
See :py:meth:`~climada.engine.impact.Impact.calc_freq_curve`
166+
167+
Raises
168+
------
169+
NotImplementedError
170+
"""
171+
172+
LOGGER.error("calc_freq_curve is not defined for ImpactForecast")
173+
raise NotImplementedError("calc_freq_curve is not defined for ImpactForecast")
174+
175+
def _check_sizes(self):
176+
"""Check sizes of forecast data vs. impact data.
177+
178+
Raises
179+
------
180+
ValueError
181+
If the sizes of the forecast data do not match the
182+
:py:attr:`~climada.engine.impact.Impact.event_id`
183+
"""
184+
num_entries = len(self.event_id)
185+
size(exp_len=num_entries, var=self.member, var_name="Forecast.member")
186+
size(exp_len=num_entries, var=self.lead_time, var_name="Forecast.lead_time")
187+
188+
def select(
189+
self,
190+
event_ids=None,
191+
event_names=None,
192+
dates=None,
193+
coord_exp=None,
194+
reset_frequency=False,
195+
member=None,
196+
lead_time=None,
197+
):
198+
"""Select entries based on the parameters and return a new instance.
199+
The selection will contain the intersection of all given parameters.
200+
201+
Parameters
202+
----------
203+
member : Sequence of ints
204+
Ensemble members to select
205+
lead_time : Sequence of numpy.timedelta64
206+
Lead times to select
207+
208+
Returns
209+
-------
210+
ImpactForecast
211+
212+
See Also
213+
--------
214+
:py:meth:`~climada.engine.impact.Impact.select`
215+
"""
216+
if member is not None or lead_time is not None:
217+
mask_member = (
218+
self.idx_member(member)
219+
if member is not None
220+
else np.full_like(self.member, True, dtype=bool)
221+
)
222+
mask_lead_time = (
223+
self.idx_lead_time(lead_time)
224+
if lead_time is not None
225+
else np.full_like(self.lead_time, True, dtype=bool)
226+
)
227+
event_id_from_forecast_mask = np.asarray(self.event_id)[
228+
(mask_member & mask_lead_time)
229+
]
230+
event_ids = (
231+
np.intersect1d(event_ids, event_id_from_forecast_mask)
232+
if event_ids is not None
233+
else event_id_from_forecast_mask
234+
)
235+
236+
return super().select(
237+
event_ids=event_ids,
238+
event_names=event_names,
239+
dates=dates,
240+
coord_exp=coord_exp,
241+
reset_frequency=reset_frequency,
242+
)

0 commit comments

Comments
 (0)