Skip to content

Commit 2e1f11b

Browse files
committed
Unit tests for helper (and local conftest)
1 parent f111bdf commit 2e1f11b

2 files changed

Lines changed: 753 additions & 0 deletions

File tree

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
"""
2+
This file is part of CLIMADA.
3+
4+
Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.
5+
6+
CLIMADA is free software: you can redistribute it and/or modify it under the
7+
terms of the GNU General Public License as published by the Free
8+
Software Foundation, version 3.
9+
10+
CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
11+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12+
PARTICULAR PURPOSE. See the GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License along
15+
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.
16+
---
17+
18+
A set of reusable fixtures for testing purpose.
19+
20+
The objective of this file is to provide minimalistic, understandable and consistent
21+
default objects for unit and integration testing.
22+
23+
Values are chosen such that:
24+
- Exposure value of the first points is 0. (First location should always have 0 impacts)
25+
- Category / Group id of all points is 1, except for third point, valued at 2000 (Impacts on that category are always a share of 2000)
26+
- Hazard centroids are the exposure centroids shifted by `HAZARD_JITTER` on both lon and lat.
27+
- There are 4 events, with frequencies == 0.03, 0.01, 0.006, 0.004, 0,
28+
such that impacts for RP250, 100 and 50 and 20 are at_event,
29+
(freq sorted cumulate to 1/250, 1/100, 1/50 and 1/20).
30+
- Hazard intensity is:
31+
* Event 1: zero everywhere (always no impact)
32+
* Event 2: max intensity at first centroid (also always no impact (first centroid is 0))
33+
* Event 3: half max intensity at second centroid (impact == half second centroid)
34+
* Event 4: quarter max intensity everywhere (impact == 1/4 total value)
35+
* Event 5: max intensity everywhere (but zero frequency)
36+
With max intensity set at 100
37+
- Impact function is the "identity function", x intensity is x% damages
38+
- Impact values should be:
39+
* AAI = 18 = 1000*1/2*0.006+(1000+2000+3000+4000+5000)*0.25*0.004
40+
* RP20 = event1 = 0
41+
* RP50 = event2 = 0
42+
* RP100 = event3 = 500 = 1000*1/2
43+
* RP250 = event4 = 3750 = (1000+2000+3000+4000+5000)*0.25
44+
45+
"""
46+
47+
import geopandas as gpd
48+
import numpy as np
49+
import pytest
50+
from scipy.sparse import csr_matrix
51+
52+
from climada.entity import Exposures, ImpactFunc, ImpactFuncSet
53+
from climada.hazard import Centroids, Hazard
54+
55+
# ---------------------------------------------------------------------------
56+
# Coordinate system and metadata
57+
# ---------------------------------------------------------------------------
58+
CRS_WGS84 = "EPSG:4326"
59+
60+
# ---------------------------------------------------------------------------
61+
# Exposure attributes
62+
# ---------------------------------------------------------------------------
63+
EXP_DESC = "Test exposure dataset"
64+
EXPOSURE_REF_YEAR = 2020
65+
EXPOSURE_VALUE_UNIT = "USD"
66+
VALUES = np.array([0, 1000, 2000, 3000, 4000, 5000])
67+
CATEGORIES = np.array([1, 1, 2, 1, 1, 3])
68+
69+
# Exposure coordinates
70+
EXP_LONS = np.array([4, 4.25, 4.5, 4, 4.25, 4.5])
71+
EXP_LATS = np.array([33, 33, 33, 33.25, 33.25, 33.25])
72+
73+
# ---------------------------------------------------------------------------
74+
# Hazard definition
75+
# ---------------------------------------------------------------------------
76+
HAZARD_TYPE = "TEST_HAZARD_TYPE"
77+
HAZARD_UNIT = "TEST_HAZARD_UNIT"
78+
79+
# Hazard centroid positions
80+
HAZ_JITTER = 0.1 # To test centroid matching
81+
HAZ_LONS = EXP_LONS + HAZ_JITTER
82+
HAZ_LATS = EXP_LATS + HAZ_JITTER
83+
84+
# Hazard events
85+
EVENT_IDS = np.array([1, 2, 3, 4, 5])
86+
EVENT_NAMES = ["ev1", "ev2", "ev3", "ev4", "ev5"]
87+
DATES = np.array([1, 2, 3, 4, 5])
88+
89+
# Frequency are choosen so that they cumulate nicely
90+
# to correspond to 250, 100, 50, and 20y return periods (for impacts)
91+
FREQUENCY = np.array([0.03, 0.01, 0.006, 0.004, 0.0])
92+
FREQUENCY_UNIT = "1/year"
93+
94+
# Hazard maximum intensity
95+
# 100 to match 0 to 100% idea
96+
# also in line with linear 1:1 impact function
97+
# for easy mental calculus
98+
HAZARD_MAX_INTENSITY = 100
99+
100+
# ---------------------------------------------------------------------------
101+
# Impact function
102+
# ---------------------------------------------------------------------------
103+
IMPF_ID = 1
104+
IMPF_NAME = "IMPF_1"
105+
106+
107+
@pytest.fixture
108+
def exposures_factory():
109+
def _make_exposures(
110+
values=VALUES,
111+
exp_lons=EXP_LONS,
112+
exp_lats=EXP_LATS,
113+
value_factor=1.0,
114+
ref_year=EXPOSURE_REF_YEAR,
115+
hazard_type=HAZARD_TYPE,
116+
group_id=None,
117+
crs=CRS_WGS84,
118+
impf_id=IMPF_ID,
119+
description=EXP_DESC,
120+
value_unit=EXPOSURE_VALUE_UNIT,
121+
categories=None,
122+
):
123+
gdf = gpd.GeoDataFrame(
124+
{
125+
"value": values * value_factor,
126+
f"impf_{hazard_type}": impf_id,
127+
"category": categories,
128+
"geometry": gpd.points_from_xy(exp_lons, exp_lats, crs=crs),
129+
},
130+
crs=crs,
131+
)
132+
if group_id is not None:
133+
gdf["group_id"] = group_id
134+
135+
return Exposures(
136+
data=gdf,
137+
description=description,
138+
ref_year=ref_year,
139+
value_unit=value_unit,
140+
)
141+
142+
return _make_exposures
143+
144+
145+
@pytest.fixture
146+
def exposures(exposures_factory):
147+
return exposures_factory()
148+
149+
150+
def hazard_frequency_factory(base=FREQUENCY):
151+
def _make_frequency(scale=1.0):
152+
return base * scale
153+
154+
return _make_frequency
155+
156+
157+
def hazard_frequency():
158+
return hazard_frequency_factory()
159+
160+
161+
def hazard_intensity(max_intensity=HAZARD_MAX_INTENSITY, scale=1.0):
162+
"""
163+
Intensity matrix designed for analytical expectations:
164+
- Event 1: zero
165+
- Event 2: max intensity at first centroid
166+
- Event 3: half max intensity at second centroid
167+
- Event 4: quarter max intensity everywhere
168+
"""
169+
base = csr_matrix(
170+
[
171+
[0, 0, 0, 0, 0, 0],
172+
[max_intensity, 0, 0, 0, 0, 0],
173+
[0, max_intensity / 2, 0, 0, 0, 0],
174+
[
175+
max_intensity / 4,
176+
max_intensity / 4,
177+
max_intensity / 4,
178+
max_intensity / 4,
179+
max_intensity / 4,
180+
max_intensity / 4,
181+
],
182+
[
183+
max_intensity,
184+
max_intensity,
185+
max_intensity,
186+
max_intensity,
187+
max_intensity,
188+
max_intensity,
189+
],
190+
]
191+
)
192+
193+
return base * scale
194+
195+
196+
@pytest.fixture
197+
def centroids():
198+
return Centroids(lat=HAZ_LATS, lon=HAZ_LONS, crs=CRS_WGS84)
199+
200+
201+
@pytest.fixture
202+
def hazard_factory():
203+
def _make_hazard(
204+
intensity_matrix=None,
205+
frequency_array=FREQUENCY,
206+
max_intensity=HAZARD_MAX_INTENSITY,
207+
centroids=None,
208+
intensity_scale=1.0,
209+
frequency_scale=1.0,
210+
hazard_type=HAZARD_TYPE,
211+
hazard_unit=HAZARD_UNIT,
212+
lat=HAZ_LATS,
213+
lon=HAZ_LONS,
214+
crs=CRS_WGS84,
215+
event_id=EVENT_IDS,
216+
event_name=EVENT_NAMES,
217+
date=DATES,
218+
frequency_unit=FREQUENCY_UNIT,
219+
):
220+
if intensity_matrix is None:
221+
intensity_matrix = hazard_intensity(max_intensity, intensity_scale)
222+
223+
if centroids is None:
224+
centroids = Centroids(lat=lat, lon=lon, crs=crs)
225+
226+
return Hazard(
227+
haz_type=hazard_type,
228+
units=hazard_unit,
229+
centroids=centroids,
230+
event_id=event_id,
231+
event_name=event_name,
232+
date=date,
233+
frequency=frequency_array * frequency_scale,
234+
frequency_unit=frequency_unit,
235+
intensity=intensity_matrix,
236+
)
237+
238+
return _make_hazard
239+
240+
241+
@pytest.fixture
242+
def hazard(hazard_factory):
243+
return hazard_factory()
244+
245+
246+
@pytest.fixture
247+
def impf_factory():
248+
def _make_impf(
249+
paa_scale=1.0,
250+
max_intensity=HAZARD_MAX_INTENSITY,
251+
hazard_type=HAZARD_TYPE,
252+
hazard_unit=HAZARD_UNIT,
253+
impf_id=IMPF_ID,
254+
negative_intensities=False,
255+
):
256+
intensity = np.array([0, max_intensity / 2, max_intensity])
257+
mdd = np.array([0, 0.5, 1])
258+
if negative_intensities:
259+
intensity = np.flip(intensity) * -1
260+
mdd = np.flip(mdd)
261+
return ImpactFunc(
262+
haz_type=hazard_type,
263+
intensity_unit=hazard_unit,
264+
name=IMPF_NAME,
265+
intensity=intensity,
266+
mdd=mdd,
267+
paa=np.array([1, 1, 1]) * paa_scale,
268+
id=impf_id,
269+
)
270+
271+
return _make_impf
272+
273+
274+
@pytest.fixture
275+
def linear_impact_function(impf_factory):
276+
return impf_factory()
277+
278+
279+
@pytest.fixture
280+
def impfset_factory(impf_factory):
281+
def _make_impfset(
282+
paa_scale=1.0,
283+
max_intensity=HAZARD_MAX_INTENSITY,
284+
hazard_type=HAZARD_TYPE,
285+
hazard_unit=HAZARD_UNIT,
286+
impf_id=IMPF_ID,
287+
negative_intensities=False,
288+
):
289+
return ImpactFuncSet(
290+
[
291+
impf_factory(
292+
paa_scale,
293+
max_intensity,
294+
hazard_type,
295+
hazard_unit,
296+
impf_id,
297+
negative_intensities,
298+
)
299+
]
300+
)
301+
302+
return _make_impfset
303+
304+
305+
@pytest.fixture
306+
def impfset(impfset_factory):
307+
return impfset_factory()

0 commit comments

Comments
 (0)