Skip to content

Commit fd72a29

Browse files
author
Chahan Kropf
committed
Add polynomial s shape impact function
1 parent 07f7281 commit fd72a29

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

climada/entity/impact_funcs/base.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,70 @@ def set_sigmoid_impf(self, *args, **kwargs):
273273
LOGGER.warning("The use of ImpactFunc.set_sigmoid_impf is deprecated."
274274
"Use ImpactFunc.from_sigmoid_impf instead.")
275275
self.__dict__ = ImpactFunc.from_sigmoid_impf(*args, **kwargs).__dict__
276+
277+
@classmethod
278+
def from_poly_s_shape(
279+
cls,
280+
intensity: tuple[float, float, float],
281+
threshold: float,
282+
half_point: float,
283+
upper_limit: float,
284+
exponent: float,
285+
haz_type: str,
286+
impf_id: int = 1,
287+
**kwargs):
288+
"""S-shape polynomial impact function hinging on four parameter.
289+
290+
f(I) = D(I)**exponent / (1 + D(I)**exponent) * upper_limit
291+
292+
D(I) = max[I - threshold, 0] / (half_point - threshold)
293+
294+
This function is inspired by Emanuel et al. (2011)
295+
https://doi.org/10.1175/WCAS-D-11-00007.1
296+
297+
Parameters
298+
----------
299+
intensity: tuple(float, float, float)
300+
tuple of 3 intensity numbers along np.arange(min, max, step)
301+
threshold : float
302+
Intensity threshold below which there is no impact.
303+
Should in general be larger than 0 for computational efficiency
304+
of impacts.
305+
half_point : float
306+
Intensity at which 50% of maxixmum impact is expected.
307+
Must be larger than thres.
308+
upper_limit : float
309+
Maximum impact value. Must be larger than 0.
310+
exponent: float
311+
Exponent of the polynomial. Must be larger than 0.
312+
haz_type: str
313+
Reference string for the hazard (e.g., 'TC', 'RF', 'WS', ...)
314+
impf_id : int, optional, default=1
315+
Impact function id
316+
kwargs :
317+
keyword arguments passed to ImpactFunc()
318+
319+
Returns
320+
-------
321+
impf : climada.entity.impact_funcs.ImpactFunc
322+
s-shapep polynomial impact function
323+
"""
324+
intensity = np.arange(*intensity)
325+
326+
if threshold > half_point:
327+
mdd = np.zeros_like(intensity)
328+
else:
329+
D = (intensity - threshold) / (half_point - threshold)
330+
D[D < 0] = 0
331+
mdd = D**exponent / (1 + D**exponent) * upper_limit
332+
paa = np.ones_like(intensity)
333+
334+
impf = cls(
335+
haz_type=haz_type,
336+
id=impf_id,
337+
intensity=intensity,
338+
paa=paa,
339+
mdd=mdd,
340+
**kwargs
341+
)
342+
return impf

climada/entity/impact_funcs/test/test_base.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ def test_from_step(self):
4747
self.assertEqual(imp_fun.haz_type, 'TC')
4848
self.assertEqual(imp_fun.id, 2)
4949

50-
5150
def test_from_sigmoid(self):
5251
"""Check default impact function: sigmoid function"""
5352
inten = (0, 100, 5)
@@ -60,6 +59,34 @@ def test_from_sigmoid(self):
6059
self.assertEqual(imp_fun.haz_type, 'RF')
6160
self.assertEqual(imp_fun.id, 2)
6261

62+
def test_from_poly_s_shape(self):
63+
"""Check default impact function: polynomial s-shape"""
64+
inten = (0, 5, 1)
65+
impf = ImpactFunc.from_poly_s_shape(
66+
inten, threshold=0.2, half_point=1, upper_limit=0.8, exponent=4,
67+
haz_type='RF', impf_id=2, intensity_unit='m'
68+
)
69+
correct_mdd = np.array([0, 0.4, 0.76995746, 0.79470418, 0.79843158])
70+
self.assertTrue(np.array_equal(impf.paa, np.ones(5)))
71+
np.testing.assert_array_almost_equal(impf.mdd, correct_mdd)
72+
self.assertTrue(np.array_equal(impf.intensity, np.arange(0, 5, 1)))
73+
self.assertEqual(impf.haz_type, 'RF')
74+
self.assertEqual(impf.id, 2)
75+
self.assertEqual(impf.intensity_unit, 'm')
76+
77+
# If threshold > half_point, mdd should all be 0
78+
impf = ImpactFunc.from_poly_s_shape(
79+
inten, threshold=2, half_point=1, upper_limit=0.8, exponent=4,
80+
haz_type='RF', impf_id=2, intensity_unit='m'
81+
)
82+
correct_mdd = np.array([0, 0.4, 0.76995746, 0.79470418, 0.79843158])
83+
self.assertTrue(np.array_equal(impf.paa, np.ones(5)))
84+
np.testing.assert_array_almost_equal(impf.mdd, np.zeros(5))
85+
self.assertTrue(np.array_equal(impf.intensity, np.arange(0, 5, 1)))
86+
self.assertEqual(impf.haz_type, 'RF')
87+
self.assertEqual(impf.id, 2)
88+
self.assertEqual(impf.intensity_unit, 'm')
89+
6390
# Execute Tests
6491
if __name__ == "__main__":
6592
TESTS = unittest.TestLoader().loadTestsFromTestCase(TestInterpolation)

0 commit comments

Comments
 (0)