Skip to content

Commit 3985a2b

Browse files
Merge branch 'develop' into feature/exp_demo_from_api
2 parents 85be154 + 049c442 commit 3985a2b

25 files changed

Lines changed: 1419 additions & 32 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Code freeze date: YYYY-MM-DD
1212

1313
### Added
1414

15+
- Better type hints and overloads signatures for ImpactFuncSet [#1250](https://github.com/CLIMADA-project/climada_python/pull/1250)
16+
1517
### Changed
1618
- Updated Impact Calculation Tutorial (`doc.climada_engine_Impact.ipynb`) [#1095](https://github.com/CLIMADA-project/climada_python/pull/1095).
1719

-335 Bytes
Binary file not shown.

climada/entity/exposures/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,9 @@ def __init__(
403403

404404
self.description = self._consolidate(meta, "description", description)
405405
self.ref_year = self._consolidate(meta, "ref_year", ref_year, DEF_REF_YEAR)
406+
407+
if geodata.shape[0] > 0:
408+
value_unit = self._consolidate(geodata.iloc[0], "value_unit", value_unit)
406409
self.value_unit = self._consolidate(
407410
meta, "value_unit", value_unit, DEF_VALUE_UNIT
408411
)

climada/entity/exposures/test/test_base.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def test__init__mda_in_kwargs(self):
182182
def test_read_raster_pass(self):
183183
"""from_raster"""
184184
exp = Exposures.from_raster(
185-
HAZ_DEMO_FL, window=Window(10, 20, 50, 60), attrs={"value_unit": "USD"}
185+
HAZ_DEMO_FL, window=Window(10, 20, 50, 60), attrs={"value_unit": "PKR"}
186186
)
187187
exp.check()
188188
self.assertTrue(u_coord.equal_crs(exp.crs, DEF_CRS))
@@ -204,7 +204,7 @@ def test_read_raster_pass(self):
204204
self.assertAlmostEqual(
205205
exp.gdf["value"].values.reshape((60, 50))[25, 12], 0.056825936
206206
)
207-
self.assertEqual(exp.value_unit, "USD")
207+
self.assertEqual(exp.value_unit, "PKR")
208208

209209
def test_assign_raster_pass(self):
210210
"""Test assign_centroids with raster hazard"""
@@ -429,16 +429,27 @@ def test_read_template_pass(self):
429429
exp_df = Exposures(df)
430430
# set metadata
431431
exp_df.ref_year = 2020
432-
exp_df.value_unit = "XSD"
432+
exp_df.value_unit = "PAK"
433433
exp_df.check()
434434

435+
def test_handling_unit_conflicts_pass(self):
436+
"""Check that the value_unit is correctly set when there are conflicting value_unit definitions in the data frame and the meta attribute."""
437+
df = pd.read_excel(ENT_TEMPLATE_XLS)
438+
exp_df = Exposures(df, meta={"value_unit": "XSD"}, value_unit="XSD")
439+
exp_df.check()
440+
self.assertEqual(exp_df.value_unit, "XSD")
441+
with self.assertRaises(ValueError) as cm:
442+
exp_df = Exposures(df, meta={"value_unit": "XSD"}, value_unit="PAK")
443+
with self.assertRaises(ValueError) as cm:
444+
exp_df = Exposures(df, meta={"value_unit": "PAK"}, value_unit="XSD")
445+
with self.assertRaises(ValueError) as cm:
446+
exp_df = Exposures(df, meta={"value_unit": "PAK"}, value_unit="PAK")
447+
435448
def test_io_hdf5_pass(self):
436449
"""write and read hdf5"""
437-
exp = Exposures(pd.read_excel(ENT_TEMPLATE_XLS), crs="epsg:32632")
438-
439-
# set metadata
440-
exp.ref_year = 2020
441-
exp.value_unit = "XSD"
450+
exp = Exposures(
451+
pd.read_excel(ENT_TEMPLATE_XLS), crs="epsg:32632", ref_year=2020
452+
)
442453

443454
# add another geometry column
444455
exp.data["geocol2"] = exp.data.geometry.copy(deep=True)

climada/entity/impact_funcs/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def from_step_impf(
189189
haz_type: str,
190190
mdd: tuple[float, float] = (0, 1),
191191
paa: tuple[float, float] = (1, 1),
192-
impf_id: int = 1,
192+
impf_id: int | str = 1,
193193
**kwargs,
194194
):
195195
"""Step function type impact function.
@@ -207,7 +207,7 @@ def from_step_impf(
207207
(min, max) mdd values. The default is (0, 1)
208208
paa: tuple(float, float)
209209
(min, max) paa values. The default is (1, 1)
210-
impf_id : int, optional, default=1
210+
impf_id : int|str, optional, default=1
211211
impact function id
212212
kwargs :
213213
keyword arguments passed to ImpactFunc()
@@ -250,7 +250,7 @@ def from_sigmoid_impf(
250250
k: float,
251251
x0: float,
252252
haz_type: str,
253-
impf_id: int = 1,
253+
impf_id: int | str = 1,
254254
**kwargs,
255255
):
256256
r"""Sigmoid type impact function hinging on three parameter.
@@ -320,7 +320,7 @@ def from_poly_s_shape(
320320
scale: float,
321321
exponent: float,
322322
haz_type: str,
323-
impf_id: int = 1,
323+
impf_id: int | str = 1,
324324
**kwargs,
325325
):
326326
r"""S-shape polynomial impact function hinging on four parameter.

climada/entity/impact_funcs/impact_func_set.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import copy
2525
import logging
2626
from itertools import repeat
27-
from typing import Iterable, Optional
27+
from typing import Iterable, Optional, Union, overload
2828

2929
import matplotlib.pyplot as plt
3030
import numpy as np
@@ -119,7 +119,7 @@ def clear(self):
119119
"""Reinitialize attributes."""
120120
self._data = dict() # {hazard_type : {id:ImpactFunc}}
121121

122-
def append(self, func):
122+
def append(self, func: ImpactFunc):
123123
"""Append a ImpactFunc. Overwrite existing if same id and haz_type.
124124
125125
Parameters
@@ -141,7 +141,9 @@ def append(self, func):
141141
self._data[func.haz_type] = dict()
142142
self._data[func.haz_type][func.id] = func
143143

144-
def remove_func(self, haz_type=None, fun_id=None):
144+
def remove_func(
145+
self, haz_type: Optional[str] = None, fun_id: Optional[str | int] = None
146+
):
145147
"""Remove impact function(s) with provided hazard type and/or id.
146148
If no input provided, all impact functions are removed.
147149
@@ -173,7 +175,29 @@ def remove_func(self, haz_type=None, fun_id=None):
173175
else:
174176
self._data = dict()
175177

176-
def get_func(self, haz_type=None, fun_id=None):
178+
@overload
179+
def get_func(
180+
self, haz_type: None = None, fun_id: None = None
181+
) -> dict[str, dict[Union[int, str], ImpactFunc]]: ...
182+
183+
@overload
184+
def get_func(
185+
self, haz_type: None = ..., fun_id: int | str = ...
186+
) -> list[ImpactFunc]: ...
187+
188+
@overload
189+
def get_func(
190+
self, haz_type: str = ..., fun_id: None = None
191+
) -> list[ImpactFunc]: ...
192+
193+
@overload
194+
def get_func(self, haz_type: str = ..., fun_id: int | str = ...) -> ImpactFunc: ...
195+
196+
def get_func(
197+
self, haz_type: Optional[str] = None, fun_id: Optional[int | str] = None
198+
) -> Union[
199+
ImpactFunc, list[ImpactFunc], dict[str, dict[Union[int, str], ImpactFunc]]
200+
]:
177201
"""Get ImpactFunc(s) of input hazard type and/or id.
178202
If no input provided, all impact functions are returned.
179203
@@ -209,7 +233,7 @@ def get_func(self, haz_type=None, fun_id=None):
209233
else:
210234
return self._data
211235

212-
def get_hazard_types(self, fun_id=None):
236+
def get_hazard_types(self, fun_id: Optional[str | int] = None) -> list[str]:
213237
"""Get impact functions hazard types contained for the id provided.
214238
Return all hazard types if no input id.
215239
@@ -231,7 +255,15 @@ def get_hazard_types(self, fun_id=None):
231255
haz_types.append(vul_haz)
232256
return haz_types
233257

234-
def get_ids(self, haz_type=None):
258+
@overload
259+
def get_ids(self, haz_type: None = None) -> dict[str, list[str | int]]: ...
260+
261+
@overload
262+
def get_ids(self, haz_type: str) -> list[int | str]: ...
263+
264+
def get_ids(
265+
self, haz_type: Optional[str] = None
266+
) -> dict[str, list[str | int]] | list[int | str]:
235267
"""Get impact functions ids contained for the hazard type provided.
236268
Return all ids for each hazard type if no input hazard type.
237269
@@ -256,7 +288,9 @@ def get_ids(self, haz_type=None):
256288
except KeyError:
257289
return list()
258290

259-
def size(self, haz_type=None, fun_id=None):
291+
def size(
292+
self, haz_type: Optional[str] = None, fun_id: Optional[str | int] = None
293+
) -> int:
260294
"""Get number of impact functions contained with input hazard type and
261295
/or id. If no input provided, get total number of impact functions.
262296
@@ -279,6 +313,7 @@ def size(self, haz_type=None, fun_id=None):
279313
return 1
280314
if (haz_type is not None) or (fun_id is not None):
281315
return len(self.get_func(haz_type, fun_id))
316+
282317
return sum(len(vul_list) for vul_list in self.get_ids().values())
283318

284319
def check(self):
@@ -300,7 +335,7 @@ def check(self):
300335
)
301336
vul.check()
302337

303-
def extend(self, impact_funcs):
338+
def extend(self, impact_funcs: "ImpactFuncSet"):
304339
"""Append impact functions of input ImpactFuncSet to current
305340
ImpactFuncSet. Overwrite ImpactFunc if same id and haz_type.
306341
@@ -323,7 +358,13 @@ def extend(self, impact_funcs):
323358
for _, vul in vul_dict.items():
324359
self.append(vul)
325360

326-
def plot(self, haz_type=None, fun_id=None, axis=None, **kwargs):
361+
def plot(
362+
self,
363+
haz_type: Optional[str] = None,
364+
fun_id: Optional[str | int] = None,
365+
axis=None,
366+
**kwargs,
367+
):
327368
"""Plot impact functions of selected hazard (all if not provided) and
328369
selected function id (all if not provided).
329370

climada/hazard/tc_tracks_synth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,7 @@ def _apply_decay_coeffs(track, v_rel, p_rel, land_geom, s_rel):
10491049
# if there is no further landfall, correct until the end of
10501050
# the track
10511051
end_cor = track["time"].size
1052-
rndn = 0.1 * float(np.abs(np.random.normal(size=1) * 5) + 6)
1052+
rndn = 0.1 * float(np.abs(np.random.default_rng().normal() * 5) + 6)
10531053
r_diff = (
10541054
track["central_pressure"][land_sea].values
10551055
- track["central_pressure"][land_sea - 1].values

climada/hazard/test/test_xarray.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import numpy as np
2929
import xarray as xr
3030
from pyproj import CRS
31-
from scipy.sparse import csr_matrix
31+
from scipy.sparse import csr_array, csr_matrix
3232

3333
from climada.hazard.base import Hazard
3434
from climada.util.constants import DEF_CRS
@@ -104,8 +104,8 @@ def _assert_default_types(self, hazard):
104104
self.assertIsInstance(hazard.event_id, np.ndarray)
105105
self.assertIsInstance(hazard.event_name, list)
106106
self.assertIsInstance(hazard.frequency, np.ndarray)
107-
self.assertIsInstance(hazard.intensity, csr_matrix)
108-
self.assertIsInstance(hazard.fraction, csr_matrix)
107+
self.assertIsInstance(hazard.intensity, csr_matrix | csr_array)
108+
self.assertIsInstance(hazard.fraction, csr_matrix | csr_array)
109109
self.assertIsInstance(hazard.date, np.ndarray)
110110

111111
def test_load_path(self):
@@ -149,8 +149,11 @@ def _load_and_assert(**kwargs):
149149
def test_type_error(self):
150150
"""Calling 'from_xarray_raster' with wrong data type should throw"""
151151
# Passing a DataArray
152-
with xr.open_dataset(self.netcdf_path) as dset, self.assertRaisesRegex(
153-
TypeError, "This method only supports passing xr.Dataset"
152+
with (
153+
xr.open_dataset(self.netcdf_path) as dset,
154+
self.assertRaisesRegex(
155+
TypeError, "This method only supports passing xr.Dataset"
156+
),
154157
):
155158
Hazard.from_xarray_raster(dset["intensity"], "", "")
156159

climada/hazard/xarray.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _to_csr_matrix(array: xr.DataArray) -> sparse.csr_matrix:
5858
output_dtypes=[array.dtype],
5959
)
6060
sparse_coo = array.compute().data # Load into memory
61-
return sparse_coo.tocsr() # Convert sparse.COO to scipy.sparse.csr_matrix
61+
return sparse_coo.tocsr() # Convert sparse.COO to scipy.sparse.csr_array
6262

6363

6464
# Define accessors for xarray DataArrays

climada/trajectories/__init__.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
19+
This module implements risk trajectory objects which enable computation and
20+
possibly interpolation of risk metric over multiple dates.
21+
22+
"""
23+
24+
from .interpolation import AllLinearStrategy, ExponentialExposureStrategy
25+
from .snapshot import Snapshot
26+
27+
__all__ = [
28+
"AllLinearStrategy",
29+
"ExponentialExposureStrategy",
30+
"Snapshot",
31+
]

0 commit comments

Comments
 (0)