Skip to content

Commit e197566

Browse files
committed
Update ImpactCalc tests for forecasts
1 parent d3a5642 commit e197566

File tree

1 file changed

+63
-86
lines changed

1 file changed

+63
-86
lines changed

climada/engine/test/test_impact_calc.py

Lines changed: 63 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@
5050
DATA_FOLDER.mkdir(exist_ok=True)
5151

5252

53-
@pytest.fixture(autouse=True)
54-
def exposure_fixture(n_exp=50):
53+
@pytest.fixture(params=[50, 1, 0])
54+
def exposure(request):
55+
n_exp = request.param
5556
lats = np.linspace(-10, 10, n_exp)
5657
lons = np.linspace(-10, 10, n_exp)
5758
data = gpd.GeoDataFrame(
@@ -67,19 +68,19 @@ def exposure_fixture(n_exp=50):
6768
return exposures
6869

6970

70-
@pytest.fixture(autouse=True)
71-
def hazard_fixture(exposure_fixture):
71+
@pytest.fixture
72+
def hazard(exposure):
7273
n_events = 10
7374
centroids = Centroids(
74-
lat=exposure_fixture.gdf.geometry.x,
75-
lon=exposure_fixture.gdf.geometry.y,
75+
lat=exposure.gdf.geometry.x,
76+
lon=exposure.gdf.geometry.y,
7677
)
7778
intensity = sparse.csr_matrix(
78-
np.ones((n_events, exposure_fixture.gdf.shape[0])) * 50
79+
np.ones((n_events, exposure.gdf.shape[0])) * 50
7980
) # uniform intensity
8081
haz = Hazard()
8182
haz.event_id = np.arange(n_events)
82-
haz.event_name = haz.event_id
83+
haz.event_name = haz.event_id.tolist()
8384
haz.haz_type = "TC"
8485
haz.date = haz.event_id
8586
haz.frequency_unit = "m/s"
@@ -89,24 +90,28 @@ def hazard_fixture(exposure_fixture):
8990
return haz
9091

9192

92-
@pytest.fixture(autouse=True)
93-
def hazard_forecast_fixture(hazard_fixture):
94-
n_events = hazard_fixture.size
93+
@pytest.fixture
94+
def hazard_forecast(hazard):
95+
n_events = hazard.size
9596
lead_time = pd.timedelta_range("1h", periods=n_events).to_numpy()
96-
member = np.arange(10)
97+
member = np.arange(n_events)
9798
haz_fc = HazardForecast.from_hazard(
98-
hazard=hazard_fixture,
99+
hazard=hazard,
99100
lead_time=lead_time,
100101
member=member,
101102
)
102103
return haz_fc
103104

104105

105-
@pytest.fixture(autouse=True)
106-
def impact_func_set_fixture(exposure_fixture, hazard_fixture):
106+
@pytest.fixture
107+
def impact_func_set(exposure, hazard):
107108
step_impf = ImpactFunc()
108-
step_impf.id = exposure_fixture.data[f"impf_{hazard_fixture.haz_type}"].unique()[0]
109-
step_impf.haz_type = hazard_fixture.haz_type
109+
step_impf.id = 1
110+
try:
111+
step_impf.id = exposure.data[f"impf_{hazard.haz_type}"].unique()[0]
112+
except IndexError:
113+
pass
114+
step_impf.haz_type = hazard.haz_type
110115
step_impf.name = "fixture step function"
111116
step_impf.intensity_unit = ""
112117
step_impf.intensity = np.array([0, 0.495, 0.4955, 0.5, 1, 10])
@@ -115,18 +120,12 @@ def impact_func_set_fixture(exposure_fixture, hazard_fixture):
115120
return ImpactFuncSet([step_impf])
116121

117122

118-
@pytest.fixture(autouse=True)
119-
def impact_calc_fixture(exposure_fixture, hazard_fixture, impact_func_set_fixture):
120-
imp_mat = np.ones(
121-
(
122-
len(hazard_fixture.event_id),
123-
exposure_fixture.gdf.shape[0],
124-
exposure_fixture.gdf.shape[0],
125-
)
126-
)
127-
aai_agg = np.sum(exposure_fixture.gdf["value"]) * hazard_fixture.frequency[0]
128-
eai_exp = np.ones(exposure_fixture.gdf.shape[0]) * hazard_fixture.frequency[0]
129-
at_event = np.ones(hazard_fixture.size) * np.sum(exposure_fixture.gdf["value"])
123+
@pytest.fixture
124+
def impact_calc(exposure, hazard):
125+
imp_mat = np.ones((len(hazard.event_id), exposure.gdf.shape[0]))
126+
aai_agg = np.sum(exposure.gdf["value"]) * hazard.frequency[0]
127+
eai_exp = np.ones(exposure.gdf.shape[0]) * hazard.frequency[0]
128+
at_event = np.ones(hazard.size) * np.sum(exposure.gdf["value"])
130129
return {
131130
"imp_mat": imp_mat,
132131
"aai_agg": aai_agg,
@@ -135,17 +134,18 @@ def impact_calc_fixture(exposure_fixture, hazard_fixture, impact_func_set_fixtur
135134
}
136135

137136

138-
def check_impact(self, imp, haz, exp, aai_agg, eai_exp, at_event, imp_mat_array=None):
137+
def check_impact(imp, haz, exp, aai_agg, eai_exp, at_event, imp_mat_array=None):
139138
"""Test properties of impacts"""
140-
self.assertEqual(len(haz.event_id), len(imp.at_event))
141-
self.assertIsInstance(imp, Impact)
139+
# NOTE: Correctly compares NaNs!
140+
assert len(haz.event_id) == len(imp.at_event)
141+
assert isinstance(imp, Impact)
142142
np.testing.assert_allclose(imp.coord_exp[:, 0], exp.latitude)
143143
np.testing.assert_allclose(imp.coord_exp[:, 1], exp.longitude)
144-
self.assertAlmostEqual(imp.aai_agg, aai_agg, 3)
144+
np.testing.assert_allclose(imp.aai_agg, aai_agg, rtol=1e-3)
145145
np.testing.assert_allclose(imp.eai_exp, eai_exp, rtol=1e-5)
146146
np.testing.assert_allclose(imp.at_event, at_event, rtol=1e-5)
147147
if imp_mat_array is not None:
148-
np.testing.assert_allclose(imp.imp_mat.toarray().ravel(), imp_mat_array.ravel())
148+
np.testing.assert_allclose(imp.imp_mat.todense(), imp_mat_array)
149149

150150

151151
class TestImpactCalc(unittest.TestCase):
@@ -389,7 +389,7 @@ def test_calc_impact_RF_pass(self):
389389
]
390390
)
391391
# fmt: on
392-
check_impact(self, impact, haz, exp, aai_agg, eai_exp, at_event, imp_mat_array)
392+
check_impact(impact, haz, exp, aai_agg, eai_exp, at_event, imp_mat_array)
393393

394394
def test_empty_impact(self):
395395
"""Check that empty impact is returned if no centroids match the exposures"""
@@ -400,11 +400,11 @@ def test_empty_impact(self):
400400
aai_agg = 0.0
401401
eai_exp = np.zeros(len(exp.gdf))
402402
at_event = np.zeros(HAZ.size)
403-
check_impact(self, impact, HAZ, exp, aai_agg, eai_exp, at_event, None)
403+
check_impact(impact, HAZ, exp, aai_agg, eai_exp, at_event, None)
404404

405405
impact = icalc.impact(save_mat=True, assign_centroids=False)
406406
imp_mat_array = sparse.csr_matrix((HAZ.size, len(exp.gdf))).toarray()
407-
check_impact(self, impact, HAZ, exp, aai_agg, eai_exp, at_event, imp_mat_array)
407+
check_impact(impact, HAZ, exp, aai_agg, eai_exp, at_event, imp_mat_array)
408408

409409
def test_single_event_impact(self):
410410
"""Check impact for single event"""
@@ -414,11 +414,11 @@ def test_single_event_impact(self):
414414
aai_agg = 0.0
415415
eai_exp = np.zeros(len(ENT.exposures.gdf))
416416
at_event = np.array([0])
417-
check_impact(self, impact, haz, ENT.exposures, aai_agg, eai_exp, at_event, None)
417+
check_impact(impact, haz, ENT.exposures, aai_agg, eai_exp, at_event, None)
418418
impact = icalc.impact(save_mat=True, assign_centroids=False)
419419
imp_mat_array = sparse.csr_matrix((haz.size, len(ENT.exposures.gdf))).toarray()
420420
check_impact(
421-
self, impact, haz, ENT.exposures, aai_agg, eai_exp, at_event, imp_mat_array
421+
impact, haz, ENT.exposures, aai_agg, eai_exp, at_event, imp_mat_array
422422
)
423423

424424
def test_calc_impact_save_mat_pass(self):
@@ -692,70 +692,47 @@ def test_single_exp_zero_mdr(self):
692692
imp = ImpactCalc(exp, impf_set, haz).impact(
693693
assign_centroids=False, save_mat=True
694694
)
695-
check_impact(self, imp, haz, exp, aai_agg, eai_exp, at_event, at_event)
695+
check_impact(imp, haz, exp, aai_agg, eai_exp, at_event, at_event)
696696

697697

698698
class TestImpactCalcForecast:
699699
"""Test impact calc for forecast hazard"""
700700

701-
def test_impactForecast_type(
701+
@pytest.fixture
702+
def impact_calc_forecast(self, impact_calc):
703+
"""Write NaNs to attributes that are not used"""
704+
impact_calc["aai_agg"] = np.full_like(impact_calc["aai_agg"], np.nan)
705+
impact_calc["eai_exp"] = np.full_like(impact_calc["eai_exp"], np.nan)
706+
707+
def test_impact_forecast(
702708
self,
703-
exposure_fixture,
704-
hazard_forecast_fixture,
705-
impact_func_set_fixture,
706-
impact_calc_fixture,
709+
exposure,
710+
hazard_forecast,
711+
impact_func_set,
712+
impact_calc,
713+
impact_calc_forecast,
707714
):
708715
"""Test that ImpactForecast is returned correctly"""
709-
impact = ImpactCalc(
710-
exposure_fixture, impact_func_set_fixture, hazard_forecast_fixture
711-
).impact(assign_centroids=True, save_mat=True)
716+
impact = ImpactCalc(exposure, impact_func_set, hazard_forecast).impact(
717+
assign_centroids=True, save_mat=True
718+
)
712719
# check that impact is indeed ImpactForecast
720+
impact_calc["imp_mat_array"] = impact_calc.pop("imp_mat")
721+
check_impact(imp=impact, haz=hazard_forecast, exp=exposure, **impact_calc)
713722
assert isinstance(impact, ImpactForecast)
714-
np.testing.assert_array_equal(
715-
impact.lead_time, hazard_forecast_fixture.lead_time
716-
)
717-
assert impact.lead_time.dtype == hazard_forecast_fixture.lead_time.dtype
718-
np.testing.assert_array_equal(impact.member, hazard_forecast_fixture.member)
723+
np.testing.assert_array_equal(impact.lead_time, hazard_forecast.lead_time)
724+
assert impact.lead_time.dtype == hazard_forecast.lead_time.dtype
725+
np.testing.assert_array_equal(impact.member, hazard_forecast.member)
719726

720727
def test_impact_forecast_empty_impmat_error(
721-
self, hazard_forecast_fixture, exposure_fixture, impact_func_set_fixture
728+
self, hazard_forecast, exposure, impact_func_set
722729
):
723730
"""Test that error is raised when trying to compute impact forecast
724731
without saving impact matrix
725732
"""
726-
icalc = ImpactCalc(
727-
exposure_fixture, impact_func_set_fixture, hazard_forecast_fixture
728-
)
729-
no_impmat_exception = (
730-
"Saving impact matrix is required when using HazardForecast."
731-
"Please set save_mat=True."
732-
)
733-
with pytest.raises(ValueError) as cm:
733+
icalc = ImpactCalc(exposure, impact_func_set, hazard_forecast)
734+
with pytest.raises(ValueError, match="Saving impact matrix is required"):
734735
icalc.impact(assign_centroids=True, save_mat=False)
735-
assert no_impmat_exception == str(cm.value)
736-
737-
def test_impact_forecast_blocked_nonsense_attrs(
738-
self, hazard_forecast_fixture, exposure_fixture, impact_func_set_fixture
739-
):
740-
"""Test that nonsense attributes are blocked when computing impact forecast"""
741-
lead_time = hazard_forecast_fixture.lead_time
742-
member = hazard_forecast_fixture.member
743-
744-
impact = ImpactCalc(
745-
exposure_fixture, impact_func_set_fixture, hazard_forecast_fixture
746-
).impact(assign_centroids=True, save_mat=True)
747-
assert np.isnan(impact.aai_agg)
748-
assert np.all(np.isnan(impact.eai_exp))
749-
assert impact.eai_exp.shape == (len(exposure_fixture.gdf),)
750-
751-
# test that aai_agg and eai_exp are also nan when 0-size exp
752-
empty_exp = exposure_fixture(n_exp=0)
753-
impact_empty = ImpactCalc(
754-
exposure_fixture, impact_func_set_fixture, hazard_forecast_fixture
755-
).impact(assign_centroids=True, save_mat=True)
756-
assert np.isnan(impact_empty.aai_agg)
757-
assert np.all(np.isnan(impact_empty.eai_exp))
758-
assert impact_empty.eai_exp.shape == (len(empty_exp.gdf),)
759736

760737

761738
class TestImpactMatrixCalc(unittest.TestCase):

0 commit comments

Comments
 (0)