Skip to content

Commit

Permalink
Update ImpactFuncSet.__init__ (#568)
Browse files Browse the repository at this point in the history
* Add all ImpactFunc attributes to ImpactFunc.__init__

* Add type hints to ImpactFunc.__init__

* Update ImpactFunc classmethods to new init

* Update tests to new ImpactFunc.__init__

* Update docs on InputVar regarding ImpactFunc

* Update tutorials with new ImpactFunc.__init__

* Update ImpactFunc.__init__ docstring

* Update argument order of ImpactFunc.__init__

* Fix linter issue

* Update tests to new ImpactFunc.__init__

* Update tutorials with new ImpactFunc.__init__

* Enable passing impact funcs to ImpactFuncSet constructor

* Update code base to new ImpactFuncSet constructor

* Fix unit test of ImpactFuncSet

Remove initialization with set because it does not guarantee insertion
order.

* undo re-plotting

* Incorporate new ImpactFuncSet.__init__ method

Co-authored-by: emanuel-schmid <[email protected]>
  • Loading branch information
peanutfun and emanuel-schmid authored Nov 18, 2022
1 parent c9892d7 commit eb22779
Show file tree
Hide file tree
Showing 18 changed files with 103 additions and 125 deletions.
3 changes: 1 addition & 2 deletions climada/engine/calibration_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ def calib_instance(hazard, exposure, impact_func, df_out=pd.DataFrame(),
DataFrame with modelled impact written to rows for each year
or event.
"""
ifs = ImpactFuncSet()
ifs.append(impact_func)
ifs = ImpactFuncSet([impact_func])
impacts = ImpactCalc(exposures=exposure, impfset=ifs, hazard=hazard)\
.impact(assign_centroids=False)
if yearly_impact: # impact per year
Expand Down
6 changes: 2 additions & 4 deletions climada/engine/test/test_forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ def test_Forecast_calc_properties(self):
#vulnerability
#generate vulnerability
impact_function = ImpfStormEurope.from_welker()
impact_function_set = ImpactFuncSet()
impact_function_set.append(impact_function)
impact_function_set = ImpactFuncSet([impact_function])
#create and calculate Forecast
forecast = Forecast({dt.datetime(2018,1,1): haz}, expo, impact_function_set)
forecast.calc()
Expand Down Expand Up @@ -130,8 +129,7 @@ def test_Forecast_plot(self):
#vulnerability
#generate vulnerability
impact_function = ImpfStormEurope.from_welker()
impact_function_set = ImpactFuncSet()
impact_function_set.append(impact_function)
impact_function_set = ImpactFuncSet([impact_function])
#create and calculate Forecast
forecast = Forecast({dt.datetime(2018,1,2): haz1,
dt.datetime(2017,12,31): haz2},
Expand Down
3 changes: 1 addition & 2 deletions climada/engine/unsequa/input_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ def imp_fun_tc(G, v_half, vmin, k, _id=1):
mdd=mdd,
paa=paa)
imp_fun.check()
impf_set = ImpactFuncSet()
impf_set.append(imp_fun)
impf_set = ImpactFuncSet([imp_fun])
return impf_set
distr_dict = {"G": sp.stats.uniform(0.8, 1),
"v_half": sp.stats.uniform(50, 100),
Expand Down
3 changes: 1 addition & 2 deletions climada/engine/unsequa/test/test_unsequa.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ def impf_dem(x_paa=1, x_mdd=1):
paa = np.arange(0, len(intensity)) / len(intensity) * x_paa
impf = ImpactFunc(haz_type, id, intensity, mdd, paa, intensity_unit)
impf.check()
impf_set = ImpactFuncSet()
impf_set.append(impf)
impf_set = ImpactFuncSet([impf])
return impf_set


Expand Down
66 changes: 40 additions & 26 deletions climada/entity/impact_funcs/impact_func_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import copy
import logging
from typing import Optional, Iterable
from itertools import repeat
import numpy as np
import pandas as pd
Expand Down Expand Up @@ -61,7 +62,7 @@
}
"""MATLAB variable names"""

class ImpactFuncSet():
class ImpactFuncSet:
"""Contains impact functions of type ImpactFunc. Loads from
files with format defined in FILE_EXT.
Expand All @@ -74,29 +75,47 @@ class ImpactFuncSet():
directly accessed. Use the class methods instead.
"""

def __init__(self):
"""Empty initialization.
def __init__(
self,
impact_funcs: Optional[Iterable[ImpactFunc]] = None,
tag: Optional[Tag] = None
):
"""Initialization.
Build an impact function set from an iterable of ImpactFunc.
Parameters
----------
impact_funcs : iterable of ImpactFunc, optional
An iterable (list, set, array, ...) of ImpactFunc.
tag : climada.entity.tag.Tag, optional
The entity tag of this object.
Examples
--------
Fill impact functions with values and check consistency data:
>>> fun_1.haz_type = 'TC'
>>> fun_1.id = 3
>>> fun_1.intensity = np.array([0, 20])
>>> fun_1.paa = np.array([0, 1])
>>> fun_1.mdd = np.array([0, 0.5])
>>> fun_1 = ImpactFunc(haz_type, id, intensity, mdd, paa)
>>> imp_fun = ImpactFuncSet()
>>> imp_fun.append(fun_1)
>>> intensity = np.array([0, 20])
>>> paa = np.array([0, 1])
>>> mdd = np.array([0, 0.5])
>>> fun_1 = ImpactFunc("TC", 3, intensity, mdd, paa)
>>> imp_fun = ImpactFuncSet([fun_1])
>>> imp_fun.check()
Read impact functions from file and checks consistency data.
Read impact functions from file and check data consistency.
>>> imp_fun = ImpactFuncSet.from_excel(ENT_TEMPLATE_XLS)
>>> imp_fun = ImpactFuncSet()
>>> imp_fun.read(ENT_TEMPLATE_XLS)
Todo
----
* Automatically check this object if impact_funcs is not None.
"""
self.clear()
if tag is not None:
self.tag = tag
if impact_funcs is not None:
for impf in impact_funcs:
self.append(impf)

def clear(self):
"""Reinitialize attributes."""
Expand Down Expand Up @@ -359,16 +378,13 @@ def from_excel(cls, file_name, description='', var_names=None):
Returns
-------
ImpFuncSet
ImpactFuncSet
"""
if var_names is None:
var_names = DEF_VAR_EXCEL
imp_func_set = cls()
dfr = pd.read_excel(file_name, var_names['sheet_name'])

imp_func_set.clear()
imp_func_set.tag.file_name = str(file_name)
imp_func_set.tag.description = description
imp_func_set = cls(tag=Tag(str(file_name), description))
imp_func_set._fill_dfr(dfr, var_names)
return imp_func_set

Expand Down Expand Up @@ -423,9 +439,6 @@ def _get_hdf5_str(imp, idxs, file_name, var_name):
return prev_str

imp = u_hdf5.read(file_name)
impf_set = cls()
impf_set.tag.file_name = str(file_name)
impf_set.tag.description = description

try:
imp = imp[var_names['sup_field_name']]
Expand All @@ -434,6 +447,7 @@ def _get_hdf5_str(imp, idxs, file_name, var_name):
try:
imp = imp[var_names['field_name']]
funcs_idx = _get_hdf5_funcs(imp, file_name, var_names)
impact_funcs = []
for imp_key, imp_rows in funcs_idx.items():
# Store arguments in a dict (missing ones will be default)
impf_kwargs = dict()
Expand All @@ -455,11 +469,11 @@ def _get_hdf5_str(imp, idxs, file_name, var_name):
imp[var_names['var_name']['inten']], imp_rows)
impf_kwargs["mdd"] = np.take(imp[var_names['var_name']['mdd']], imp_rows)
impf_kwargs["paa"] = np.take(imp[var_names['var_name']['paa']], imp_rows)
impf_set.append(ImpactFunc(**impf_kwargs))
impact_funcs.append(ImpactFunc(**impf_kwargs))
except KeyError as err:
raise KeyError("Not existing variable: %s" % str(err)) from err

return impf_set
return cls(impact_funcs, Tag(str(file_name), description))

def read_mat(self, *args, **kwargs):
"""This function is deprecated, use ImpactFuncSet.from_mat instead."""
Expand Down Expand Up @@ -540,8 +554,8 @@ def _get_xls_funcs(dfr, var_names):
# check that the unit of the intensity is the same, if provided
try:
if len(df_func[var_names['col_name']['unit']].unique()) != 1:
raise ValueError('Impact function with two different \
intensity units.')
raise ValueError('Impact function with two different'
' intensity units.')
impf_kwargs["intensity_unit"] = df_func[var_names['col_name']
['unit']].values[0]
except KeyError:
Expand Down
84 changes: 37 additions & 47 deletions climada/entity/impact_funcs/test/test_imp_fun_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import numpy as np

from climada import CONFIG
from climada.entity.impact_funcs.impact_func_set import ImpactFuncSet, ImpactFunc
from climada.entity.impact_funcs.impact_func_set import ImpactFuncSet, ImpactFunc, Tag
from climada.util.constants import ENT_TEMPLATE_XLS, ENT_DEMO_TODAY

ENT_TEST_MAT = CONFIG.exposures.test_data.dir().joinpath('demo_today.mat')
Expand Down Expand Up @@ -65,17 +65,13 @@ def test_add_wrong_error(self):

def test_remove_func_pass(self):
"""Test remove_func removes ImpactFunc of ImpactFuncSet correcty."""
imp_fun = ImpactFuncSet()
vulner_1 = ImpactFunc("TC", 1)
imp_fun.append(vulner_1)
imp_fun = ImpactFuncSet([ImpactFunc("TC", 1)])
imp_fun.remove_func()
self.assertEqual(0, len(imp_fun._data))

def test_remove_wrong_error(self):
"""Test error is raised when invalid inputs."""
imp_fun = ImpactFuncSet()
vulner_1 = ImpactFunc("TC", 1)
imp_fun.append(vulner_1)
imp_fun = ImpactFuncSet([ImpactFunc("TC", 1)])
with self.assertLogs('climada.entity.impact_funcs.impact_func_set', level='WARNING') as cm:
imp_fun.remove_func('FL')
self.assertIn('No ImpactFunc with hazard FL.', cm.output[0])
Expand All @@ -85,9 +81,7 @@ def test_remove_wrong_error(self):

def test_get_hazards_pass(self):
"""Test get_hazard_types function."""
imp_fun = ImpactFuncSet()
vulner_1 = ImpactFunc("TC", 1)
imp_fun.append(vulner_1)
imp_fun = ImpactFuncSet([ImpactFunc("TC", 1)])
self.assertEqual(1, len(imp_fun.get_hazard_types()))
self.assertEqual(['TC'], imp_fun.get_hazard_types())

Expand Down Expand Up @@ -133,9 +127,7 @@ def test_get_ids_pass(self):

def test_get_ids_wrong_zero(self):
"""Test get_ids method with wrong inputs."""
imp_fun = ImpactFuncSet()
vulner_1 = ImpactFunc("WS", 56)
imp_fun.append(vulner_1)
imp_fun = ImpactFuncSet([ImpactFunc("WS", 56)])
self.assertEqual([], imp_fun.get_ids('TC'))

def test_get_func_pass(self):
Expand Down Expand Up @@ -167,9 +159,7 @@ def test_get_func_pass(self):

def test_get_func_wrong_error(self):
"""Test get_func method with wrong inputs."""
imp_fun = ImpactFuncSet()
vulner_1 = ImpactFunc("WS", 56)
imp_fun.append(vulner_1)
imp_fun = ImpactFuncSet([ImpactFunc("WS", 56)])
self.assertEqual([], imp_fun.get_func('TC'))

def test_size_pass(self):
Expand Down Expand Up @@ -242,6 +232,28 @@ def test_append_pass(self):
self.assertIn(3, imp_fun._data['TC'].keys())
self.assertIn(3, imp_fun._data['FL'].keys())

def test_init_with_iterable(self):
"""Check that initializing with iterables works"""
def _check_contents(imp_fun):
self.assertEqual(imp_fun.size("TC"), 2)
self.assertEqual(imp_fun.size("FL"), 1)
self.assertEqual(imp_fun.size(fun_id=1), 1)
self.assertEqual(imp_fun.size(fun_id=3), 2)
np.testing.assert_array_equal(imp_fun.get_ids("TC"), [1, 3])
np.testing.assert_array_equal(imp_fun.get_ids("FL"), [3])

# Initialize with empty list
impf_set = ImpactFuncSet([])
self.assertEqual(impf_set.size("TC"), 0)
self.assertFalse(impf_set.get_ids("TC"))

# Initialize with list
_check_contents(ImpactFuncSet(
[ImpactFunc("TC", 1), ImpactFunc("TC", 3), ImpactFunc("FL", 3)]))
# Initialize with tuple
_check_contents(ImpactFuncSet(
(ImpactFunc("TC", 1), ImpactFunc("TC", 3), ImpactFunc("FL", 3))))

def test_remove_add_pass(self):
"""Test ImpactFunc can be added after removing."""
imp_fun = ImpactFuncSet()
Expand All @@ -261,25 +273,23 @@ class TestChecker(unittest.TestCase):
"""Test loading funcions from the ImpactFuncSet class"""
def test_check_wrongPAA_fail(self):
"""Wrong PAA definition"""
imp_fun = ImpactFuncSet()
intensity = np.array([1, 2, 3])
mdd = np.array([1, 2, 3])
paa = np.array([1, 2])
vulner = ImpactFunc("TC", 1, intensity, mdd, paa)
imp_fun.append(vulner)
imp_fun = ImpactFuncSet([vulner])

with self.assertRaises(ValueError) as cm:
imp_fun.check()
self.assertIn('Invalid ImpactFunc.paa size: 3 != 2.', str(cm.exception))

def test_check_wrongMDD_fail(self):
"""Wrong MDD definition"""
imp_fun = ImpactFuncSet()
intensity = np.array([1, 2, 3])
mdd = np.array([1, 2])
paa = np.array([1, 2, 3])
vulner = ImpactFunc("TC", 1, intensity, mdd, paa)
imp_fun.append(vulner)
imp_fun = ImpactFuncSet([vulner])

with self.assertRaises(ValueError) as cm:
imp_fun.check()
Expand All @@ -290,18 +300,9 @@ class TestExtend(unittest.TestCase):
def test_extend_to_empty_same(self):
"""Extend ImpactFuncSet to empty one."""
imp_fun = ImpactFuncSet()
imp_fun_add = ImpactFuncSet()
vulner_1 = ImpactFunc("TC", 1)
imp_fun_add.append(vulner_1)

vulner_2 = ImpactFunc("TC", 3)
imp_fun_add.append(vulner_2)

vulner_3 = ImpactFunc("FL", 3)
imp_fun_add.append(vulner_3)

imp_fun_add.tag.file_name = 'file1.txt'

imp_fun_add = ImpactFuncSet(
(ImpactFunc("TC", 1), ImpactFunc("TC", 3), ImpactFunc("FL", 3)),
Tag('file1.txt'))
imp_fun.extend(imp_fun_add)
imp_fun.check()

Expand All @@ -313,12 +314,9 @@ def test_extend_to_empty_same(self):

def test_extend_equal_same(self):
"""Extend the same ImpactFuncSet. The inital ImpactFuncSet is obtained."""
imp_fun = ImpactFuncSet()
vulner_1 = ImpactFunc("TC", 1)
imp_fun.append(vulner_1)

imp_fun_add = ImpactFuncSet()
imp_fun_add.append(vulner_1)
imp_fun = ImpactFuncSet([vulner_1])
imp_fun_add = ImpactFuncSet([vulner_1])

imp_fun.extend(imp_fun_add)
imp_fun.check()
Expand All @@ -339,16 +337,8 @@ def test_extend_different_extend(self):
vulner_3 = ImpactFunc("FL", 3)
imp_fun.append(vulner_3)

imp_fun_add = ImpactFuncSet()
vulner_1 = ImpactFunc("TC", 1)
imp_fun_add.append(vulner_1)

vulner_2 = ImpactFunc("WS", 1)
imp_fun_add.append(vulner_2)

vulner_3 = ImpactFunc("FL", 3)
imp_fun_add.append(vulner_3)

imp_fun_add = ImpactFuncSet(
(ImpactFunc("TC", 1), ImpactFunc("WS", 1), ImpactFunc("FL", 3)))
imp_fun.extend(imp_fun_add)
imp_fun.check()

Expand Down
3 changes: 1 addition & 2 deletions climada/entity/measures/test/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ def test_change_imp_func_pass(self):
meas = MeasureSet.from_mat(ENT_TEST_MAT)
act_1 = meas.get_measure(name='Mangroves')[0]

imp_set = ImpactFuncSet()
haz_type = 'XX'
idx = 1
intensity = np.arange(10, 100, 10)
Expand All @@ -62,7 +61,7 @@ def test_change_imp_func_pass(self):
0.398500000000000, 0.657000000000000, 1.000000000000000,
1.000000000000000, 1.000000000000000])
imp_tc = ImpactFunc(haz_type, idx, intensity, mdd, paa)
imp_set.append(imp_tc)
imp_set = ImpactFuncSet([imp_tc])
new_imp = act_1._change_imp_func(imp_set).get_func('XX')[0]

self.assertTrue(np.array_equal(new_imp.intensity, np.array([4., 24., 34., 44.,
Expand Down
3 changes: 1 addition & 2 deletions climada/util/test/test_lines_polys_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@
GDF_POINT = EXP_POINT.gdf

IMPF = ImpfStormEurope.from_welker()
IMPF_SET = ImpactFuncSet()
IMPF_SET.append(IMPF)
IMPF_SET = ImpactFuncSet([IMPF])

COL_CHANGING = ['value', 'latitude', 'longitude', 'geometry', 'geometry_orig']

Expand Down
Loading

0 comments on commit eb22779

Please sign in to comment.