Skip to content

Commit d5778d9

Browse files
committed
Merge remote-tracking branch 'origin/develop' into feature/option-appraisal-costincome
2 parents a56dcc6 + 7094124 commit d5778d9

22 files changed

Lines changed: 4537 additions & 96 deletions

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ Code freeze date: YYYY-MM-DD
1212

1313
### Added
1414

15-
- Adds `CostIncome` class and related tutorial for future new `Measure` class. [#1277](https://github.com/CLIMADA-project/climada_python/pull/1277)
15+
- 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
- Adds `MeasureConfig` and related dataclasses for new `Measure` object retrocompatibility and (de)serialization capabilities [#1276](https://github.com/CLIMADA-project/climada_python/pull/1276)
18+
- Adds `CostIncome` class and related tutorial for future new `Measure` class. [#1277](https://github.com/CLIMADA-project/climada_python/pull/1277)
1719
- Better type hints and overloads signatures for ImpactFuncSet [#1250](https://github.com/CLIMADA-project/climada_python/pull/1250)
1820
- Add inter- and extrapolation options to `ImpactFreqCurve` with method `interpolate` [#1252](https://github.com/CLIMADA-project/climada_python/pull/1252)
1921
- Add `trajectory` module to ease the handling of multiple risk assessment at different point in time, and enable interpolation in between. [#1197](https://github.com/CLIMADA-project/climada_python/pull/1197), [#1198](https://github.com/CLIMADA-project/climada_python/pull/1198), [#1199](https://github.com/CLIMADA-project/climada_python/pull/1199), [#1200](https://github.com/CLIMADA-project/climada_python/pull/1197), [#1201](https://github.com/CLIMADA-project/climada_python/pull/1197)
2022

2123
### Changed
24+
2225
- Updated Impact Calculation Tutorial (`doc.climada_engine_Impact.ipynb`) [#1095](https://github.com/CLIMADA-project/climada_python/pull/1095).
2326
- Makes current `measure` module a legacy module, moving it to `_legacy_measure`, to retain compatibility with `CostBenefit` class and various tests. [#1274](https://github.com/CLIMADA-project/climada_python/pull/1274)
2427

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@
88
option appraisal. Designed by a large scientific community, it helps reasearchers, policymakers, and businesses analyse the impacts of natural hazards and
99
explore adaptation strategies.
1010

11-
As of today, CLIMADA provides global coverage of major climate-related extreme-weather hazards at high resolution (4x4km) via a [data API](https://climada.ethz.ch/data-api/v1/docs) For select hazards, historic and probabilistic events sets, for past, present and future climate exist at distinct time horizons.
12-
You will find a repository containing scientific peer-reviewed articles that explain software components implemented in CLIMADA [here](https://github.com/CLIMADA-project/climada_papers).
11+
## Overview
1312

1413
CLIMADA is divided into two parts (two repositories):
1514

16-
1. the core [climada_python](https://github.com/CLIMADA-project/climada_python) contains all the modules necessary for the probabilistic impact, the averted damage, uncertainty and forecast calculations. Data for hazard, exposures and impact functions can be obtained from the [data API](https://github.com/CLIMADA-project/climada_python/blob/main/doc/tutorial/climada_util_api_client.ipynb). [Litpop](https://github.com/CLIMADA-project/climada_python/blob/main/doc/tutorial/climada_entity_LitPop.ipynb) is included as demo Exposures module, and [Tropical cyclones](https://github.com/CLIMADA-project/climada_python/blob/main/doc/tutorial/climada_hazard_TropCyclone.ipynb) is included as a demo Hazard module.
17-
2. the petals [climada_petals](https://github.com/CLIMADA-project/climada_petals) contains all the modules for generating data (e.g., TC_Surge, WildFire, OpenStreeMap, ...). Most development is done here. The petals builds-upon the core and does not work as a stand-alone.
15+
1. [Core (`climada_python`)](https://github.com/CLIMADA-project/climada_python) contains all modules necessary for computing probabilistic impact and averted damage, performing uncertainty and sensitivity analysis, and calibrating impact functions. Demonstration data for hazard, exposures and impact functions can be obtained from the [data API](https://github.com/CLIMADA-project/climada_python/blob/main/doc/user-guide/climada_util_api_client.ipynb). [Litpop](https://github.com/CLIMADA-project/climada_python/blob/main/doc/user-guide/climada_entity_LitPop.ipynb) is included as demo Exposures module, and [Tropical cyclones](https://github.com/CLIMADA-project/climada_python/blob/main/doc/user-guide/climada_hazard_TropCyclone.ipynb) is included as a demo Hazard module.
16+
2. [Petals (`climada_petals`)](https://github.com/CLIMADA-project/climada_petals) contains all the modules for generating data (e.g., TC_Surge, WildFire, OpenStreeMap, ...) and advanced applications (e.g., CatBond simulation, Supply chain propagation, ...). Most development is done here. Petals is an extension of Core and does **not** work as a stand-alone module.
1817

19-
For new users, we recommend to begin with the core (1) and the [tutorials](https://github.com/CLIMADA-project/climada_python/tree/main/doc/tutorial) therein.
18+
For new users, we recommend to begin with Core (1) and the [tutorials](https://github.com/CLIMADA-project/climada_python/tree/main/doc/user-guide) therein.
2019

21-
This is the Python version of CLIMADA - please see [here](https://github.com/davidnbresch/climada) for backward compatibility with the MATLAB version.
20+
A [publication repository](https://github.com/CLIMADA-project/climada_papers) contains case-study examples from scientific, peer-reviewed articles that further illustrate the use of CLIMADA.
21+
22+
CLIMADA provides global coverage of major climate-related extreme-weather hazards at high resolution (4x4km) via a [data API](https://climada.ethz.ch/data-api/v1/docs) for select hazards, historic and probabilistic events sets, for past, present and future climate exist at distinct time horizons.
23+
24+
This is the Python version of CLIMADA - For backward compatibility with the MATLAB version please see https://github.com/davidnbresch/climada.
2225

2326
## Getting started
2427

@@ -31,25 +34,25 @@ mamba install -c conda-forge climada
3134
```
3235

3336
It is **highly recommended** to install CLIMADA into a **separate** Conda environment.
34-
See the [installation guide](https://climada-python.readthedocs.io/en/latest/guide/install.html) for further information.
37+
See the [installation guide](https://climada-python.readthedocs.io/en/latest/getting-started/install.html) for further information.
3538

36-
Follow the [tutorials](https://climada-python.readthedocs.io/en/stable/tutorial/1_main_climada.html) in a Jupyter Notebook to see what can be done with CLIMADA and how.
39+
Follow the [tutorials](https://climada-python.readthedocs.io/en/stable/user-guide/1_main_climada.html) in a Jupyter Notebook to see what can be done with CLIMADA and how.
3740

3841
## Documentation
3942

40-
The online documentation is available on [Read the Docs](https://climada-python.readthedocs.io/en/stable/).The documentation of each release version of CLIMADA can be accessed separately through the drop-down menu at the bottom of the left sidebar. Additionally, the version 'stable' refers to the most recent release (installed via `conda`), and 'latest' refers to the latest unstable development version (the `develop` branch).
43+
The online documentation is available on [Read the Docs](https://climada-python.readthedocs.io/en/stable/). The documentation of each release version of CLIMADA can be accessed separately through the drop-down menu at the bottom of the left sidebar. Additionally, the version 'stable' refers to the most recent release (installed via `conda`), and 'latest' refers to the latest unstable development version (the `develop` branch).
4144

42-
CLIMADA python:
45+
CLIMADA Core (`climada_python`):
4346

4447
- [online (recommended)](https://climada-python.readthedocs.io/en/latest/)
4548
- [PDF file](https://climada-python.readthedocs.io/_/downloads/en/stable/pdf/)
46-
- [core Tutorials on GitHub](https://github.com/CLIMADA-project/climada_python/tree/main/doc/tutorial)
49+
- [Core tutorials on GitHub](https://github.com/CLIMADA-project/climada_python/tree/main/doc/user-guide)
4750

48-
CLIMADA petals:
51+
CLIMADA Petals (`climada_petals`):
4952

5053
- [online (recommended)](https://climada-petals.readthedocs.io/en/latest/)
5154
- [PDF file](https://climada-petals.readthedocs.io/_/downloads/en/stable/pdf/)
52-
- [petals Tutorials on GitHub](https://github.com/CLIMADA-project/climada_petals/tree/main/doc/tutorial)
55+
- [Petals tutorials on GitHub](https://github.com/CLIMADA-project/climada_petals/tree/main/doc/tutorial)
5356

5457
The documentation can also be [built locally](https://climada-python.readthedocs.io/en/latest/README.html).
5558

@@ -59,7 +62,7 @@ See the [Citation Guide](https://climada-python.readthedocs.io/en/latest/misc/ci
5962

6063
Please use the following logo if you are presenting results obtained with or through CLIMADA:
6164

62-
![https://github.com/CLIMADA-project/climada_python/blob/main/doc/guide/img/CLIMADA_logo_QR.png](https://github.com/CLIMADA-project/climada_python/blob/main/doc/guide/img/CLIMADA_logo_QR.png?raw=true)
65+
![https://github.com/CLIMADA-project/climada_python/blob/main/doc/guide/img/CLIMADA_logo_QR.png](https://github.com/CLIMADA-project/climada_python/blob/main/doc/development/img/CLIMADA_logo_QR.png?raw=true)
6366

6467
## Contributing
6568

climada/engine/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
from .cost_benefit import *
2323
from .impact import *
2424
from .impact_calc import *
25+
from .impact_forecast import ImpactForecast

climada/engine/impact.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,7 @@ def __init__(
188188
)
189189
if len(self.coord_exp) != len(self.eai_exp):
190190
raise AttributeError(
191-
"Number of exposures points is different from"
192-
"number of eai_exp values"
191+
"Number of exposures points is different fromnumber of eai_exp values"
193192
)
194193
if imp_mat is not None:
195194
self.imp_mat = imp_mat
@@ -1226,8 +1225,9 @@ def plot_rp_imp(
12261225

12271226
impacts_stats_vals = impacts_stats.values[:, 1:].T.astype(float)
12281227
if not log10_scale:
1229-
min_impact, max_impact = np.nanmin(impacts_stats_vals), np.nanmax(
1230-
impacts_stats_vals
1228+
min_impact, max_impact = (
1229+
np.nanmin(impacts_stats_vals),
1230+
np.nanmax(impacts_stats_vals),
12311231
)
12321232
kwargs.update(
12331233
{
@@ -1419,6 +1419,8 @@ def write_attribute(group, name, value):
14191419

14201420
def write_dataset(group, name, value):
14211421
"""Write a dataset"""
1422+
if name == "lead_time":
1423+
value = value.astype("timedelta64[ns]").astype("int64")
14221424
group.create_dataset(name, data=value, dtype=_str_type_helper(value))
14231425

14241426
def write_dict(group, name, value):
@@ -1459,7 +1461,6 @@ def write_csr(group, name, value):
14591461

14601462
# Open file in write mode
14611463
with h5py.File(file_path, "w") as file:
1462-
14631464
# Now write all attributes
14641465
# NOTE: Remove leading underscore to write '_tot_value' as regular attribute
14651466
for name, value in self.__dict__.items():
@@ -1600,13 +1601,18 @@ def from_excel(cls, file_name):
16001601
def read_excel(self, *args, **kwargs):
16011602
"""This function is deprecated, use Impact.from_excel instead."""
16021603
LOGGER.warning(
1603-
"The use of Impact.read_excel is deprecated."
1604-
"Use Impact.from_excel instead."
1604+
"The use of Impact.read_excel is deprecated.Use Impact.from_excel instead."
16051605
)
16061606
self.__dict__ = Impact.from_excel(*args, **kwargs).__dict__
16071607

16081608
@classmethod
1609-
def from_hdf5(cls, file_path: Union[str, Path]):
1609+
def from_hdf5(
1610+
cls,
1611+
file_path: Union[str, Path],
1612+
*,
1613+
add_scalar_attrs: Iterable[str] | None = None,
1614+
add_array_attrs: Iterable[str] | None = None,
1615+
):
16101616
"""Create an impact object from an H5 file.
16111617
16121618
This assumes a specific layout of the file. If values are not found in the
@@ -1651,6 +1657,10 @@ def from_hdf5(cls, file_path: Union[str, Path]):
16511657
----------
16521658
file_path : str or Path
16531659
The file path of the file to read.
1660+
add_scalar_attrs : Iterable of str, optional
1661+
Additional scalar attributes to read from file. Defaults to None.
1662+
add_array_attrs : Iterable of str, optional
1663+
Additional array attributes to read from file. Defaults to None.
16541664
16551665
Returns
16561666
-------
@@ -1659,7 +1669,6 @@ def from_hdf5(cls, file_path: Union[str, Path]):
16591669
"""
16601670
kwargs = dict()
16611671
with h5py.File(file_path, "r") as file:
1662-
16631672
# Impact matrix
16641673
if "imp_mat" in file:
16651674
impact_matrix = file["imp_mat"]
@@ -1679,17 +1688,27 @@ def from_hdf5(cls, file_path: Union[str, Path]):
16791688
# Scalar attributes
16801689
scalar_attrs = set(
16811690
("crs", "tot_value", "unit", "aai_agg", "frequency_unit", "haz_type")
1682-
).intersection(file.attrs.keys())
1691+
)
1692+
if add_scalar_attrs is not None:
1693+
scalar_attrs = scalar_attrs.union(add_scalar_attrs)
1694+
scalar_attrs = scalar_attrs.intersection(file.attrs.keys())
16831695
kwargs.update({attr: file.attrs[attr] for attr in scalar_attrs})
16841696

16851697
# Array attributes
16861698
# NOTE: Need [:] to copy array data. Otherwise, it would be a view that is
16871699
# invalidated once we close the file.
16881700
array_attrs = set(
16891701
("event_id", "date", "coord_exp", "eai_exp", "at_event", "frequency")
1690-
).intersection(file.keys())
1702+
)
1703+
if add_array_attrs is not None:
1704+
array_attrs = array_attrs.union(add_array_attrs)
1705+
array_attrs = array_attrs.intersection(file.keys())
16911706
kwargs.update({attr: file[attr][:] for attr in array_attrs})
1692-
1707+
# correct lead_time attribut to timedelta
1708+
if "lead_time" in kwargs:
1709+
kwargs["lead_time"] = np.array(file["lead_time"][:]).astype(
1710+
"timedelta64[ns]"
1711+
)
16931712
# Special handling for 'event_name' because it should be a list of strings
16941713
if "event_name" in file:
16951714
# pylint: disable=no-member
@@ -2196,9 +2215,12 @@ def stack_attribute(attr_name: str) -> np.ndarray:
21962215
imp_mat = sparse.vstack(imp_mats)
21972216

21982217
# Concatenate other attributes
2199-
kwargs = {
2200-
attr: stack_attribute(attr) for attr in ("date", "frequency", "at_event")
2201-
}
2218+
concat_attrs = {
2219+
name.lstrip("_") # Private attributes with getter/setter
2220+
for name, value in first_imp.__dict__.items()
2221+
if isinstance(value, np.ndarray)
2222+
}.difference(("event_id", "coord_exp", "eai_exp", "aai_agg"))
2223+
kwargs = {attr: stack_attribute(attr) for attr in concat_attrs}
22022224

22032225
# Get remaining attributes from first impact object in list
22042226
return cls(

climada/engine/impact_calc.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
from climada import CONFIG
3131
from climada.engine.impact import Impact
32+
from climada.engine.impact_forecast import ImpactForecast
33+
from climada.hazard.forecast import HazardForecast
3234

3335
LOGGER = logging.getLogger(__name__)
3436

@@ -217,7 +219,7 @@ def _return_impact(self, imp_mat_gen, save_mat):
217219
218220
Returns
219221
-------
220-
Impact
222+
Impact or ImpactForecast
221223
Impact Object initialize from the impact matrix
222224
223225
See Also
@@ -230,12 +232,31 @@ def _return_impact(self, imp_mat_gen, save_mat):
230232
at_event, eai_exp, aai_agg = self.risk_metrics(
231233
imp_mat, self.hazard.frequency
232234
)
235+
if isinstance(self.hazard, HazardForecast):
236+
eai_exp = np.full_like(eai_exp, np.nan, dtype=eai_exp.dtype)
237+
aai_agg = np.nan
238+
LOGGER.warning(
239+
"eai_exp and aai_agg are undefined with forecasts. "
240+
"Setting them to NaN arrays."
241+
)
242+
233243
else:
244+
if isinstance(self.hazard, HazardForecast):
245+
raise ValueError(
246+
"Saving impact matrix is required when using HazardForecast."
247+
"Please set save_mat=True."
248+
)
234249
imp_mat = None
235250
at_event, eai_exp, aai_agg = self.stitch_risk_metrics(imp_mat_gen)
236-
return Impact.from_eih(
251+
252+
impact = Impact.from_eih(
237253
self.exposures, self.hazard, at_event, eai_exp, aai_agg, imp_mat
238254
)
255+
if isinstance(self.hazard, HazardForecast):
256+
return ImpactForecast.from_impact(
257+
impact, self.hazard.lead_time, self.hazard.member
258+
)
259+
return impact
239260

240261
def _return_empty(self, save_mat):
241262
"""
@@ -248,21 +269,37 @@ def _return_empty(self, save_mat):
248269
249270
Returns
250271
-------
251-
Impact
272+
Impact or ImpactForecast
252273
Empty impact object with correct array sizes.
253274
"""
254275
at_event = np.zeros(self.n_events)
255-
eai_exp = np.zeros(self.n_exp_pnt)
256-
aai_agg = 0.0
276+
if isinstance(self.hazard, HazardForecast):
277+
eai_exp = np.full(self.n_exp_pnt, np.nan)
278+
aai_agg = np.nan
279+
else:
280+
eai_exp = np.zeros(self.n_exp_pnt)
281+
aai_agg = 0.0
282+
257283
if save_mat:
258284
imp_mat = sparse.csr_matrix(
259285
(self.n_events, self.n_exp_pnt), dtype=np.float64
260286
)
261287
else:
288+
if isinstance(self.hazard, HazardForecast):
289+
raise ValueError(
290+
"Saving impact matrix is required when using HazardForecast. "
291+
"Please set save_mat=True."
292+
)
262293
imp_mat = None
263-
return Impact.from_eih(
294+
295+
impact = Impact.from_eih(
264296
self.exposures, self.hazard, at_event, eai_exp, aai_agg, imp_mat
265297
)
298+
if isinstance(self.hazard, HazardForecast):
299+
return ImpactForecast.from_impact(
300+
impact, self.hazard.lead_time, self.hazard.member
301+
)
302+
return impact
266303

267304
def minimal_exp_gdf(
268305
self, impf_col, assign_centroids, ignore_cover, ignore_deductible

0 commit comments

Comments
 (0)