Skip to content

Commit b00509f

Browse files
committed
fix merge conflicts
2 parents 464e592 + bb99794 commit b00509f

File tree

6 files changed

+356
-26
lines changed

6 files changed

+356
-26
lines changed

climada/engine/impact.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,9 +2208,12 @@ def stack_attribute(attr_name: str) -> np.ndarray:
22082208
imp_mat = sparse.vstack(imp_mats)
22092209

22102210
# Concatenate other attributes
2211-
kwargs = {
2212-
attr: stack_attribute(attr) for attr in ("date", "frequency", "at_event")
2213-
}
2211+
concat_attrs = {
2212+
name.lstrip("_") # Private attributes with getter/setter
2213+
for name, value in first_imp.__dict__.items()
2214+
if isinstance(value, np.ndarray)
2215+
}.difference(("event_id", "coord_exp", "eai_exp", "aai_agg"))
2216+
kwargs = {attr: stack_attribute(attr) for attr in concat_attrs}
22142217

22152218
# Get remaining attributes from first impact object in list
22162219
return cls(

climada/engine/impact_forecast.py

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import logging
2323

2424
import numpy as np
25+
import scipy.sparse as sparse
2526

2627
from ..util import log_level
2728
from ..util.checker import size
@@ -185,6 +186,123 @@ def _check_sizes(self):
185186
size(exp_len=num_entries, var=self.member, var_name="Forecast.member")
186187
size(exp_len=num_entries, var=self.lead_time, var_name="Forecast.lead_time")
187188

189+
def _reduce_attrs(self, event_name: str):
190+
"""
191+
Reduce the attributes of an ImpactForecast to a single value.
192+
193+
Attributes are modified as follows:
194+
- lead_time: set to NaT
195+
- member: set to -1
196+
- event_id: set to 0
197+
- event_name: set to the name of the reduction method (default)
198+
- date: set to 0
199+
- frequency: set to 1
200+
201+
Parameters
202+
----------
203+
event_name : str
204+
The event name given to the reduced data.
205+
"""
206+
reduced_attrs = {
207+
"lead_time": np.array([np.timedelta64("NaT")]),
208+
"member": np.array([-1]),
209+
"event_id": np.array([0]),
210+
"event_name": np.array([event_name]),
211+
"date": np.array([0]),
212+
"frequency": np.array([1]),
213+
}
214+
215+
return reduced_attrs
216+
217+
def min(self):
218+
"""
219+
Reduce the impact matrix and at_event of an ImpactForecast to the minimum
220+
value.
221+
222+
Parameters
223+
----------
224+
None
225+
226+
Returns
227+
-------
228+
ImpactForecast
229+
An ImpactForecast object with the min impact matrix and at_event.
230+
"""
231+
red_imp_mat = self.imp_mat.min(axis=0).tocsr()
232+
red_at_event = np.array([red_imp_mat.sum()])
233+
return ImpactForecast(
234+
frequency_unit=self.frequency_unit,
235+
coord_exp=self.coord_exp,
236+
crs=self.crs,
237+
eai_exp=self.eai_exp,
238+
at_event=red_at_event,
239+
tot_value=self.tot_value,
240+
aai_agg=self.aai_agg,
241+
unit=self.unit,
242+
imp_mat=red_imp_mat,
243+
haz_type=self.haz_type,
244+
**self._reduce_attrs("min"),
245+
)
246+
247+
def max(self):
248+
"""
249+
Reduce the impact matrix and at_event of an ImpactForecast to the maximum
250+
value.
251+
252+
Parameters
253+
----------
254+
None
255+
256+
Returns
257+
-------
258+
ImpactForecast
259+
An ImpactForecast object with the max impact matrix and at_event.
260+
"""
261+
red_imp_mat = self.imp_mat.max(axis=0).tocsr()
262+
red_at_event = np.array([red_imp_mat.sum()])
263+
return ImpactForecast(
264+
frequency_unit=self.frequency_unit,
265+
coord_exp=self.coord_exp,
266+
crs=self.crs,
267+
eai_exp=self.eai_exp,
268+
at_event=red_at_event,
269+
tot_value=self.tot_value,
270+
aai_agg=self.aai_agg,
271+
unit=self.unit,
272+
imp_mat=red_imp_mat,
273+
haz_type=self.haz_type,
274+
**self._reduce_attrs("max"),
275+
)
276+
277+
def mean(self):
278+
"""
279+
Reduce the impact matrix and at_event of an ImpactForecast to the mean value.
280+
281+
Parameters
282+
----------
283+
None
284+
285+
Returns
286+
-------
287+
ImpactForecast
288+
An ImpactForecast object with the mean impact matrix and at_event.
289+
"""
290+
red_imp_mat = sparse.csr_matrix(self.imp_mat.mean(axis=0))
291+
red_at_event = np.array([red_imp_mat.sum()])
292+
return ImpactForecast(
293+
frequency_unit=self.frequency_unit,
294+
coord_exp=self.coord_exp,
295+
crs=self.crs,
296+
eai_exp=self.eai_exp,
297+
at_event=red_at_event,
298+
tot_value=self.tot_value,
299+
aai_agg=self.aai_agg,
300+
unit=self.unit,
301+
imp_mat=red_imp_mat,
302+
haz_type=self.haz_type,
303+
**self._reduce_attrs("mean"),
304+
)
305+
188306
def select(
189307
self,
190308
event_ids=None,
@@ -205,10 +323,6 @@ def select(
205323
lead_time : Sequence of numpy.timedelta64
206324
Lead times to select
207325
208-
Returns
209-
-------
210-
ImpactForecast
211-
212326
See Also
213327
--------
214328
:py:meth:`~climada.engine.impact.Impact.select`

climada/engine/test/test_impact_forecast.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,27 @@ def test_no_select(self, impact_forecast, impact_kwargs):
222222
assert imp_fc_select.imp_mat.shape == (0, num_centroids)
223223

224224

225-
@pytest.mark.skip("Concat from base class does not work")
226-
def test_impact_forecast_concat(impact_forecast, member):
225+
def test_impact_forecast_concat(impact_forecast, member, lead_time):
227226
"""Check if Impact.concat works on the derived class"""
228227
impact_fc = ImpactForecast.concat(
229228
[impact_forecast, impact_forecast], reset_event_ids=True
230229
)
231230
npt.assert_array_equal(impact_fc.member, np.concatenate([member, member]))
231+
npt.assert_array_equal(impact_fc.lead_time, np.concatenate([lead_time, lead_time]))
232+
npt.assert_array_equal(
233+
impact_fc.event_id, np.arange(impact_fc.imp_mat.shape[0]) + 1
234+
)
235+
npt.assert_array_equal(impact_fc.event_name, impact_forecast.event_name * 2)
236+
npt.assert_array_equal(
237+
impact_fc.imp_mat.toarray(),
238+
np.vstack(
239+
(impact_forecast.imp_mat.toarray(), impact_forecast.imp_mat.toarray())
240+
),
241+
)
232242

233243

234244
def test_impact_forecast_blocked_methods(impact_forecast):
235-
"""Check if blocked methods raise NotImplementedError"""
245+
"""Check if ImpactForecast.exceedance_freq_curve raises NotImplementedError"""
236246
with pytest.raises(NotImplementedError):
237247
impact_forecast.local_exceedance_impact(np.array([10, 50, 100]))
238248

@@ -241,3 +251,40 @@ def test_impact_forecast_blocked_methods(impact_forecast):
241251

242252
with pytest.raises(NotImplementedError):
243253
impact_forecast.calc_freq_curve(np.array([10, 50, 100]))
254+
255+
256+
@pytest.fixture
257+
def impact_forecast_stats(impact_kwargs, lead_time, member):
258+
max_index = 4
259+
for key, val in impact_kwargs.items():
260+
if isinstance(val, (np.ndarray, list)):
261+
impact_kwargs[key] = val[:max_index]
262+
elif isinstance(val, csr_matrix):
263+
impact_kwargs[key] = val[:max_index, :]
264+
impact_kwargs["imp_mat"] = csr_matrix([[1, 0], [0, 1], [3, 2], [2, 3]])
265+
impact_kwargs["at_event"] = np.array([1, 1, 5, 5])
266+
return ImpactForecast(
267+
lead_time=lead_time[:max_index], member=member[:max_index], **impact_kwargs
268+
)
269+
270+
271+
@pytest.mark.parametrize("attr", ["min", "mean", "max"])
272+
def test_impact_forecast_min_mean_max(impact_forecast_stats, attr):
273+
"""Check mean, min, and max methods for ImpactForecast"""
274+
imp_fc_reduced = getattr(impact_forecast_stats, attr)()
275+
276+
# assert imp_mat
277+
npt.assert_array_equal(
278+
imp_fc_reduced.imp_mat.todense(),
279+
getattr(impact_forecast_stats.imp_mat.todense(), attr)(axis=0),
280+
)
281+
at_event_expected = {"min": [0], "mean": [3], "max": [6]}
282+
npt.assert_array_equal(imp_fc_reduced.at_event, at_event_expected[attr])
283+
284+
# check that attributes where reduced correctly
285+
npt.assert_array_equal(np.isnat(imp_fc_reduced.lead_time), [True])
286+
npt.assert_array_equal(imp_fc_reduced.member, [-1])
287+
npt.assert_array_equal(imp_fc_reduced.event_name, [attr])
288+
npt.assert_array_equal(imp_fc_reduced.event_id, [0])
289+
npt.assert_array_equal(imp_fc_reduced.frequency, [1])
290+
npt.assert_array_equal(imp_fc_reduced.date, [0])

climada/hazard/forecast.py

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from typing import Any, Dict, List, Optional
2525

2626
import numpy as np
27+
import scipy.sparse as sparse
2728
import xarray as xr
2829

2930
from climada.hazard.xarray import HazardXarrayReader
@@ -174,6 +175,125 @@ def _check_sizes(self):
174175
size(exp_len=num_entries, var=self.member, var_name="Forecast.member")
175176
size(exp_len=num_entries, var=self.lead_time, var_name="Forecast.lead_time")
176177

178+
def _reduce_attrs(self, event_name: str):
179+
"""
180+
Reduce the attributes of a HazardForecast to a single value.
181+
182+
Attributes are modified as follows:
183+
- lead_time: set to NaT
184+
- member: set to -1
185+
- event_id: set to 0
186+
- event_name: set to the name of the reduction method (default)
187+
- date: set to 0
188+
- frequency: set to 1
189+
190+
Parameters
191+
----------
192+
event_name : str
193+
The event_name given to the reduced data.
194+
"""
195+
reduced_attrs = {
196+
"lead_time": np.array([np.timedelta64("NaT")]),
197+
"member": np.array([-1]),
198+
"event_id": np.array([0]),
199+
"event_name": np.array([event_name]),
200+
"date": np.array([0]),
201+
"frequency": np.array([1]),
202+
"orig": np.array([True]),
203+
}
204+
205+
return reduced_attrs
206+
207+
def min(self):
208+
"""
209+
Reduce the intensity and fraction of a HazardForecast to the minimum
210+
value.
211+
212+
Parameters
213+
----------
214+
None
215+
216+
Returns
217+
-------
218+
HazardForecast
219+
A HazardForecast object with the min intensity and fraction.
220+
"""
221+
red_intensity = self.intensity.min(axis=0).tocsr()
222+
red_fraction = self.fraction.min(axis=0).tocsr()
223+
return HazardForecast(
224+
haz_type=self.haz_type,
225+
pool=self.pool,
226+
units=self.units,
227+
centroids=self.centroids,
228+
frequency_unit=self.frequency_unit,
229+
intensity=red_intensity,
230+
fraction=red_fraction,
231+
**self._reduce_attrs("min"),
232+
)
233+
234+
def max(self):
235+
"""
236+
Reduce the intensity and fraction of a HazardForecast to the maximum
237+
value.
238+
239+
Parameters
240+
----------
241+
None
242+
243+
Returns
244+
-------
245+
HazardForecast
246+
A HazardForecast object with the min intensity and fraction.
247+
"""
248+
red_intensity = self.intensity.max(axis=0).tocsr()
249+
red_fraction = self.fraction.max(axis=0).tocsr()
250+
return HazardForecast(
251+
haz_type=self.haz_type,
252+
pool=self.pool,
253+
units=self.units,
254+
centroids=self.centroids,
255+
frequency_unit=self.frequency_unit,
256+
intensity=red_intensity,
257+
fraction=red_fraction,
258+
**self._reduce_attrs("max"),
259+
)
260+
261+
def mean(self):
262+
"""
263+
Reduce the intensity and fraction of a HazardForecast to the mean value.
264+
265+
Parameters
266+
----------
267+
None
268+
269+
Returns
270+
-------
271+
HazardForecast
272+
A HazardForecast object with the min intensity and fraction.
273+
"""
274+
red_intensity = sparse.csr_matrix(self.intensity.mean(axis=0))
275+
red_fraction = sparse.csr_matrix(self.fraction.mean(axis=0))
276+
return HazardForecast(
277+
haz_type=self.haz_type,
278+
pool=self.pool,
279+
units=self.units,
280+
centroids=self.centroids,
281+
frequency_unit=self.frequency_unit,
282+
intensity=red_intensity,
283+
fraction=red_fraction,
284+
**self._reduce_attrs("mean"),
285+
)
286+
287+
@classmethod
288+
def concat(cls, haz_list: list):
289+
"""Concatenate multiple HazardForecast instances and return a new object"""
290+
if len(haz_list) == 0:
291+
return cls()
292+
hazard = Hazard.concat(haz_list)
293+
lead_time = np.concatenate(tuple(haz.lead_time for haz in haz_list))
294+
member = np.concatenate(tuple(haz.member for haz in haz_list))
295+
return cls.from_hazard(hazard, lead_time=lead_time, member=member)
296+
177297
def select(
178298
self,
179299
member=None,
@@ -197,10 +317,6 @@ def select(
197317
lead_time : Sequence of numpy.timedelta64
198318
Lead times to select
199319
200-
Returns
201-
-------
202-
HazardForecast
203-
204320
See Also
205321
--------
206322
:py:meth:`~climada.hazard.base.Hazard.select`

0 commit comments

Comments
 (0)