Skip to content

Commit d487c27

Browse files
committed
Merge branch 'develop' into forecast-class
2 parents a0fd081 + 60d3957 commit d487c27

55 files changed

Lines changed: 4495 additions & 1007 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ Code freeze date: YYYY-MM-DD
1313
### Added
1414

1515
- Support for ensemble forecast data as hazard via `HazardForecast` and `ImpactForecast`, see `doc.user_guide.climada_engine_ImpactForecast.ipynb` [#1115](https://github.com/CLIMADA-project/climada_python/pull/)
16+
- Better type hints and overloads signatures for ImpactFuncSet [#1250](https://github.com/CLIMADA-project/climada_python/pull/1250)
1617

1718
### Changed
1819

1920
- Updated Impact Calculation Tutorial (`doc.climada_engine_Impact.ipynb`) [#1095](https://github.com/CLIMADA-project/climada_python/pull/1095).
2021

2122
### Fixed
2223

24+
- Fixed asset count in impact logging message [#1195](https://github.com/CLIMADA-project/climada_python/pull/1195).
25+
2326
### Deprecated
2427

2528
### Removed
29+
- `climada.util.earth_engine.py` Google Earth Engine methods did not facilitate direct use of GEE data in CLIMADA. Code was relocated to [climada-snippets](https://github.com/CLIMADA-project/climada-snippets). [#1109](https://github.com/CLIMADA-project/climada_python/pull/1109)
30+
- `doc.climada_util_earth_engine.ipynb` Tutorial about GEE not relevant to CLIMADA Core. Tutorial notebook was relocated to [climada-snippets](https://github.com/CLIMADA-project/climada-snippets). [#1109](https://github.com/CLIMADA-project/climada_python/pull/1109)
2631

2732
## 6.1.0
2833

@@ -155,9 +160,9 @@ Removed:
155160
- `climada.entity.impact_funcs.trop_cyclone.ImpfSetTropCyclone.get_impf_id_regions_per_countries` function [#1034](https://github.com/CLIMADA-project/climada_python/pull/1034)
156161
- `climada.hazard.tc_tracks.BasinBoundsStorm` Enum class `climada.hazard.tc_tracks.subset_by_basin` function [#1031](https://github.com/CLIMADA-project/climada_python/pull/1031)
157162
- `climada.hazard.tc_tracks.TCTracks.subset_years` function [#1023](https://github.com/CLIMADA-project/climada_python/pull/1023)
158-
-`climada.hazard.tc_tracks.compute_track_density` function, `climada.hazard.tc_tracks.compute_genesis_density` function, `climada.hazard.plot.plot_track_density` function
163+
- `climada.hazard.tc_tracks.compute_track_density` function, `climada.hazard.tc_tracks.compute_genesis_density` function, `climada.hazard.plot.plot_track_density` function
159164
[#1003](https://github.com/CLIMADA-project/climada_python/pull/1003)
160-
-`climada.hazard.tc_tracks.TCTracks.from_FAST` function, add Australia basin (AU) [#993](https://github.com/CLIMADA-project/climada_python/pull/993)
165+
- `climada.hazard.tc_tracks.TCTracks.from_FAST` function, add Australia basin (AU) [#993](https://github.com/CLIMADA-project/climada_python/pull/993)
161166
- Add `osm-flex` package to CLIMADA core [#981](https://github.com/CLIMADA-project/climada_python/pull/981)
162167
- `doc.tutorial.climada_entity_Exposures_osm.ipynb` tutorial explaining how to use `osm-flex` with CLIMADA
163168
- `climada.util.coordinates.bounding_box_global` function [#980](https://github.com/CLIMADA-project/climada_python/pull/980)
-335 Bytes
Binary file not shown.

climada/engine/impact_calc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def impact(
188188
return self._return_empty(save_mat)
189189
LOGGER.info(
190190
"Calculating impact for %s assets (>0) and %s events.",
191-
exp_gdf.size,
191+
len(exp_gdf),
192192
self.n_events,
193193
)
194194
imp_mat_gen = self.imp_mat_gen(exp_gdf, impf_col)

climada/engine/test/test_impact_calc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ def test_calc_insured_impact_no_insurance(self):
530530
self.assertEqual(
531531
logs.output,
532532
[
533-
"INFO:climada.engine.impact_calc:Calculating impact for 150 assets (>0) and 14450 events."
533+
"INFO:climada.engine.impact_calc:Calculating impact for 50 assets (>0) and 14450 events."
534534
],
535535
)
536536
self.assertEqual(icalc.n_events, len(impact.at_event))

climada/engine/unsequa/test/test_unsequa.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,18 @@
4141
from climada.entity import Exposures, ImpactFunc, ImpactFuncSet
4242
from climada.entity.entity_def import Entity
4343
from climada.hazard import Hazard
44-
from climada.util.api_client import Client
44+
from climada.test import get_test_file
4545
from climada.util.constants import (
4646
ENT_DEMO_FUTURE,
4747
ENT_DEMO_TODAY,
48-
EXP_DEMO_H5,
4948
HAZ_DEMO_H5,
5049
TEST_UNC_OUTPUT_COSTBEN,
5150
TEST_UNC_OUTPUT_IMPACT,
5251
)
5352

54-
test_unc_output_impact = Client().get_dataset_file(
55-
name=TEST_UNC_OUTPUT_IMPACT, status="test_dataset"
56-
)
57-
test_unc_output_costben = Client().get_dataset_file(
58-
name=TEST_UNC_OUTPUT_COSTBEN, status="test_dataset"
59-
)
53+
EXP_DEMO_H5 = get_test_file("exp_demo_today", file_format="hdf5")
54+
test_unc_output_impact = get_test_file(TEST_UNC_OUTPUT_IMPACT)
55+
test_unc_output_costben = get_test_file(TEST_UNC_OUTPUT_COSTBEN)
6056

6157

6258
def impf_dem(x_paa=1, x_mdd=1):
@@ -578,7 +574,7 @@ def test_calc_sensitivity_all_pass(self):
578574
"sensitivity_kwargs": {"S": 10, "seed": 12345},
579575
"test_param_name": ["x_exp", 0],
580576
"test_si_name": ["CV", 16],
581-
"test_si_value": [0.25000, 2],
577+
"test_si_value": [0.250000, 2],
582578
},
583579
"hdmr": {
584580
"sampling_method": "saltelli",
@@ -587,7 +583,7 @@ def test_calc_sensitivity_all_pass(self):
587583
"sensitivity_kwargs": {},
588584
"test_param_name": ["x_exp", 2],
589585
"test_si_name": ["Sa", 4],
590-
"test_si_value": [0.004658, 3],
586+
"test_si_value": [0.004649, 3],
591587
},
592588
"ff": {
593589
"sampling_method": "ff",
@@ -618,7 +614,7 @@ def test_calc_sensitivity_all_pass(self):
618614
},
619615
"test_param_name": ["x_exp", 0],
620616
"test_si_name": ["dgsm", 8],
621-
"test_si_value": [1.697516e-01, 9],
617+
"test_si_value": [0.1697516, 9],
622618
},
623619
"fast": {
624620
"sampling_method": "fast_sampler",
@@ -627,7 +623,7 @@ def test_calc_sensitivity_all_pass(self):
627623
"sensitivity_kwargs": {"M": 4, "seed": 12345},
628624
"test_param_name": ["x_exp", 0],
629625
"test_si_name": ["S1_conf", 8],
630-
"test_si_value": [0.671396, 1],
626+
"test_si_value": [0.671546, 1],
631627
},
632628
"rbd_fast": {
633629
"sampling_method": "saltelli",
@@ -636,7 +632,7 @@ def test_calc_sensitivity_all_pass(self):
636632
"sensitivity_kwargs": {"M": 4, "seed": 12345},
637633
"test_param_name": ["x_exp", 0],
638634
"test_si_name": ["S1_conf", 4],
639-
"test_si_value": [0.152609, 4],
635+
"test_si_value": [0.129919, 4],
640636
},
641637
"morris": {
642638
"sampling_method": "morris",
@@ -645,7 +641,7 @@ def test_calc_sensitivity_all_pass(self):
645641
"sensitivity_kwargs": {},
646642
"test_param_name": ["x_exp", 0],
647643
"test_si_name": ["mu", 1],
648-
"test_si_value": [5066460029.63911, 8],
644+
"test_si_value": [7935400297.813827, 8],
649645
},
650646
}
651647

@@ -700,7 +696,7 @@ def test_sensitivity_method(
700696
haz_unc,
701697
sensitivity_method,
702698
method_params,
703-
places=2 if sensitivity_method == "rbd_fast" else 5,
699+
places=5,
704700
)
705701

706702

climada/entity/exposures/base.py

Lines changed: 14 additions & 9 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
)
@@ -644,15 +647,17 @@ def assign_centroids(
644647
645648
Caution: nearest neighbourg matching can introduce serious artefacts
646649
such as:
647-
- exposure and hazard centroids with shifted grids can lead
648-
to systematically wrong assignements.
649-
- hazard centroids covering larger areas than exposures may lead
650-
to sub-optimal matching if the threshold is too large
651-
- projected crs often diverge at the anti-meridian and close points
652-
on either side will be at a large distance. For proper handling
653-
of the anti-meridian please use degree coordinates in EPSG:4326.
654-
This might be relevant for countries like the Fidji or the US that
655-
cross the anti-meridian.
650+
651+
- exposure and hazard centroids with shifted grids can lead
652+
to systematically wrong assignements.
653+
- hazard centroids covering larger areas than exposures may lead
654+
to sub-optimal matching if the threshold is too large
655+
- projected crs often diverge at the anti-meridian and close points
656+
on either side will be at a large distance. For proper handling
657+
of the anti-meridian please use degree coordinates in EPSG:4326.
658+
This might be relevant for countries like the Fidji or the US that
659+
cross the anti-meridian.
660+
656661
657662
Users are free to implement their own matching alrogithm and save the
658663
matching centroid index in the appropriate column ``centr_[hazard.HAZ_TYPE]``.

climada/entity/exposures/test/test_base.py

Lines changed: 15 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,23 @@ 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+
435444
def test_io_hdf5_pass(self):
436445
"""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"
446+
exp = Exposures(
447+
pd.read_excel(ENT_TEMPLATE_XLS), crs="epsg:32632", ref_year=2020
448+
)
442449

443450
# add another geometry column
444451
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

0 commit comments

Comments
 (0)