Skip to content

Commit a9c45d7

Browse files
committed
Merge branch 'develop' into calibrate-impact-functions
2 parents cab68c4 + db36b43 commit a9c45d7

24 files changed

+3595
-1070
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
steps:
2727
-
2828
name: Checkout Repo
29-
uses: actions/checkout@v3
29+
uses: actions/checkout@v4
3030
-
3131
# Store the current date to use it as cache key for the environment
3232
name: Get current date
@@ -64,7 +64,7 @@ jobs:
6464
-
6565
name: Upload Coverage Reports
6666
if: always()
67-
uses: actions/upload-artifact@v3
67+
uses: actions/upload-artifact@v4
6868
with:
6969
name: coverage-report-unittests-py${{ matrix.python-version }}
7070
path: coverage/

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@
3232
* Timo Schmid
3333
* Kam Lam Yeung
3434
* Sarah Hülsen
35+
* Timo Schmid

CHANGELOG.md

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,43 @@ Code freeze date: YYYY-MM-DD
1010

1111
### Dependency Changes
1212

13+
### Added
14+
15+
### Changed
16+
17+
### Fixed
18+
19+
- Fix `util.coordinates.latlon_bounds` for cases where the specified buffer is very large so that the bounds cover more than the full longitudinal range `[-180, 180]` [#839](https://github.com/CLIMADA-project/climada_python/pull/839)
20+
- Fix `climada.hazard.trop_cyclone` for TC tracks crossing the antimeridian [#839](https://github.com/CLIMADA-project/climada_python/pull/839)
21+
22+
### Deprecated
23+
24+
### Removed
25+
26+
## 4.1.0
27+
28+
Release date: 2024-02-14
29+
30+
### Dependency Changes
31+
1332
Added:
1433

1534
- `pyproj` >=3.5
16-
- `pyarrow` >=14.0
17-
- `numexpr` >=2.8
35+
- `numexpr` >=2.9
36+
37+
Updated:
38+
39+
- `contextily` >=1.3 → >=1.5
40+
- `dask` >=2023 → >=2024
41+
- `numba` >=0.57 → >=0.59
42+
- `pandas` >=2.1 &rarr; >=2.1,<2.2
43+
- `pint` >=0.22 &rarr; >=0.23
44+
- `scikit-learn` >=1.3 &rarr; >=1.4
45+
- `scipy` >=1.11 &rarr; >=1.12
46+
- `sparse` >=0.14 &rarr; >=0.15
47+
- `xarray` >=2023.8 &rarr; >=2024.1
48+
- `overpy` =0.6 &rarr; =0.7
49+
- `peewee` =3.16.3 &rarr; =3.17.1
1850

1951
Removed:
2052

@@ -25,6 +57,9 @@ Removed:
2557
- `climada.util.calibrate` module for calibrating impact functions [#692](https://github.com/CLIMADA-project/climada_python/pull/692)
2658
- Convenience method `api_client.Client.get_dataset_file`, combining `get_dataset_info` and `download_dataset`, returning a single file objet. [#821](https://github.com/CLIMADA-project/climada_python/pull/821)
2759
- Read and Write methods to and from csv files for the `DiscRates` class. [#818](ttps://github.com/CLIMADA-project/climada_python/pull/818)
60+
- Add `CalcDeltaClimate` to unsequa module to allow uncertainty and sensitivity analysis of impact change calculations [#844](https://github.com/CLIMADA-project/climada_python/pull/844)
61+
- Add function `safe_divide` in util which handles division by zero and NaN values in the numerator or denominator [#844](https://github.com/CLIMADA-project/climada_python/pull/844)
62+
- Add reset_frequency option for the impact.select() function. [#847](https://github.com/CLIMADA-project/climada_python/pull/847)
2863

2964
### Changed
3065

@@ -45,10 +80,7 @@ Removed:
4580
- In the TropCyclone class in the Holland model 2008 and 2010 implementation, a doublecounting of translational velocity is removed [#833](https://github.com/CLIMADA-project/climada_python/pull/833)
4681
- `climada.util.test.test_finance` and `climada.test.test_engine` updated to recent input data from worldbank [#841](https://github.com/CLIMADA-project/climada_python/pull/841)
4782
- Set `nodefaults` in Conda environment specs because `defaults` are not compatible with conda-forge [#845](https://github.com/CLIMADA-project/climada_python/pull/845)
48-
49-
### Deprecated
50-
51-
### Removed
83+
- Avoid redundant calls to `np.unique` in `Impact.impact_at_reg` [#848](https://github.com/CLIMADA-project/climada_python/pull/848)
5284

5385
## 4.0.1
5486

climada/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '4.0.2-dev'
1+
__version__ = '4.1.1-dev'

climada/engine/impact.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -451,12 +451,12 @@ def impact_at_reg(self, agg_regions=None):
451451
at_reg_event = np.hstack(
452452
[
453453
self.imp_mat[:, np.where(agg_regions == reg)[0]].sum(1)
454-
for reg in np.unique(agg_reg_unique)
454+
for reg in agg_reg_unique
455455
]
456456
)
457457

458458
at_reg_event = pd.DataFrame(
459-
at_reg_event, columns=np.unique(agg_reg_unique), index=self.event_id
459+
at_reg_event, columns=agg_reg_unique, index=self.event_id
460460
)
461461

462462
return at_reg_event
@@ -1475,9 +1475,14 @@ def _cen_return_imp(imp, freq, imp_th, return_periods):
14751475

14761476
return imp_fit
14771477

1478-
def select(self,
1479-
event_ids=None, event_names=None, dates=None,
1480-
coord_exp=None):
1478+
def select(
1479+
self,
1480+
event_ids=None,
1481+
event_names=None,
1482+
dates=None,
1483+
coord_exp=None,
1484+
reset_frequency=False
1485+
):
14811486
"""
14821487
Select a subset of events and/or exposure points from the impact.
14831488
If multiple input variables are not None, it returns all the impacts
@@ -1509,6 +1514,9 @@ def select(self,
15091514
coord_exp : np.array, optional
15101515
Selection of exposures coordinates [lat, lon] (in degrees)
15111516
The default is None.
1517+
reset_frequency : bool, optional
1518+
Change frequency of events proportional to difference between first and last
1519+
year (old and new). Assumes annual frequency values. Default: False.
15121520
15131521
Raises
15141522
------
@@ -1580,6 +1588,19 @@ def select(self,
15801588
LOGGER.info("The total value cannot be re-computed for a "
15811589
"subset of exposures and is set to None.")
15821590

1591+
# reset frequency if date span has changed (optional):
1592+
if reset_frequency:
1593+
if self.frequency_unit not in ['1/year', 'annual', '1/y', '1/a']:
1594+
LOGGER.warning("Resetting the frequency is based on the calendar year of given"
1595+
" dates but the frequency unit here is %s. Consider setting the frequency"
1596+
" manually for the selection or changing the frequency unit to %s.",
1597+
self.frequency_unit, DEF_FREQ_UNIT)
1598+
year_span_old = np.abs(dt.datetime.fromordinal(self.date.max()).year -
1599+
dt.datetime.fromordinal(self.date.min()).year) + 1
1600+
year_span_new = np.abs(dt.datetime.fromordinal(imp.date.max()).year -
1601+
dt.datetime.fromordinal(imp.date.min()).year) + 1
1602+
imp.frequency = imp.frequency * year_span_old / year_span_new
1603+
15831604
# cast frequency vector into 2d array for sparse matrix multiplication
15841605
freq_mat = imp.frequency.reshape(len(imp.frequency), 1)
15851606
# .A1 reduce 1d matrix to 1d array

climada/engine/test/test_impact.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import h5py
2828
from pyproj import CRS
2929
from rasterio.crs import CRS as rCRS
30+
import datetime as dt
3031

3132
from climada.entity.entity_def import Entity
3233
from climada.hazard.base import Hazard
@@ -67,6 +68,24 @@ def dummy_impact():
6768
haz_type="TC",
6869
)
6970

71+
def dummy_impact_yearly():
72+
"""Return an impact containing events in multiple years"""
73+
imp = dummy_impact()
74+
75+
years = np.arange(2010,2010+len(imp.date))
76+
77+
# Edit the date and frequency
78+
imp.date = np.array([dt.date(year,1,1).toordinal() for year in years])
79+
imp.frequency_unit = "1/year"
80+
imp.frequency = np.ones(len(years))/len(years)
81+
82+
# Calculate the correct expected annual impact
83+
freq_mat = imp.frequency.reshape(len(imp.frequency), 1)
84+
imp.eai_exp = imp.imp_mat.multiply(freq_mat).sum(axis=0).A1
85+
imp.aai_agg = imp.eai_exp.sum()
86+
87+
return imp
88+
7089

7190
class TestImpact(unittest.TestCase):
7291
""""Test initialization and more"""
@@ -868,6 +887,22 @@ def test_select_imp_map_fail(self):
868887
with self.assertRaises(ValueError):
869888
imp.select(event_ids=[0], event_names=[1, 'two'], dates=(0, 2))
870889

890+
def test_select_reset_frequency(self):
891+
"""Test that reset_frequency option works correctly"""
892+
893+
imp = dummy_impact_yearly() # 6 events, 1 per year
894+
895+
# select first 4 events
896+
n_yr = 4
897+
sel_imp = imp.select(dates=(imp.date[0],imp.date[n_yr-1]), reset_frequency=True)
898+
899+
# check frequency-related attributes
900+
np.testing.assert_array_equal(sel_imp.frequency, [1/n_yr]*n_yr)
901+
self.assertEqual(sel_imp.aai_agg,imp.at_event[0:n_yr].sum()/n_yr)
902+
np.testing.assert_array_equal(sel_imp.eai_exp,
903+
imp.imp_mat[0:n_yr,:].todense().sum(axis=0).A1/n_yr)
904+
905+
871906
class TestConvertExp(unittest.TestCase):
872907
def test__build_exp(self):
873908
"""Test that an impact set can be converted to an exposure"""

climada/engine/unsequa/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
from .calc_base import *
2323
from .calc_impact import *
2424
from .calc_cost_benefit import *
25+
from .calc_delta_climate import *

climada/engine/unsequa/calc_base.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ def __init__(self):
8585
"""
8686
Empty constructor to be overwritten by subclasses
8787
"""
88-
pass
8988

9089
def check_distr(self):
9190
"""
@@ -118,7 +117,6 @@ def check_distr(self):
118117
distr_dict[input_param_name] = input_param_func
119118
return True
120119

121-
122120
@property
123121
def input_vars(self):
124122
"""
@@ -179,7 +177,6 @@ def est_comp_time(self, n_samples, time_one_run, processes=None):
179177
"been assigned to exp before defining input_var, ..."
180178
"\n If computation cannot be reduced, consider using"
181179
" a surrogate model https://www.uqlab.com/", time_one_run)
182-
183180
total_time = n_samples * time_one_run / processes
184181
LOGGER.info("\n\nEstimated computaion time: %s\n",
185182
dt.timedelta(seconds=total_time))
@@ -323,21 +320,23 @@ def sensitivity(self, unc_output, sensitivity_method = 'sobol',
323320
324321
Parameters
325322
----------
326-
unc_output : climada.engine.uncertainty.unc_output.UncOutput
323+
unc_output : climada.engine.unsequa.UncOutput
327324
Uncertainty data object in which to store the sensitivity indices
328325
sensitivity_method : str, optional
329-
Sensitivity analysis method from SALib.analyse. Possible choices: 'fast', 'rbd_fact',
330-
'morris', 'sobol', 'delta', 'ff'. Note that in Salib, sampling methods and sensitivity
331-
analysis methods should be used in specific pairs:
326+
sensitivity analysis method from SALib.analyse
327+
Possible choices:
328+
'fast', 'rbd_fact', 'morris', 'sobol', 'delta', 'ff'
329+
The default is 'sobol'.
330+
Note that in Salib, sampling methods and sensitivity analysis
331+
methods should be used in specific pairs.
332332
https://salib.readthedocs.io/en/latest/api.html
333-
Default: 'sobol'
334333
sensitivity_kwargs: dict, optional
335334
Keyword arguments of the chosen SALib analyse method.
336335
The default is to use SALib's default arguments.
337336
338337
Returns
339338
-------
340-
sens_output : climada.engine.uncertainty.unc_output.UncOutput()
339+
sens_output : climada.engine.unsequa.UncOutput
341340
Uncertainty data object with all the sensitivity indices,
342341
and all the uncertainty data copied over from unc_output.
343342
@@ -379,6 +378,7 @@ def sensitivity(self, unc_output, sensitivity_method = 'sobol',
379378

380379
return sens_output
381380

381+
382382
def _multiprocess_chunksize(samples_df, processes):
383383
"""Divides the samples into chunks for multiprocesses computing
384384
@@ -405,6 +405,7 @@ def _multiprocess_chunksize(samples_df, processes):
405405
samples_df.shape[0] / processes
406406
).astype(int)
407407

408+
408409
def _transpose_chunked_data(metrics):
409410
"""Transposes the output metrics lists from one list per
410411
chunk of samples to one list per output metric
@@ -434,6 +435,7 @@ def _transpose_chunked_data(metrics):
434435
for x in zip(*metrics)
435436
]
436437

438+
437439
def _sample_parallel_iterator(samples, chunksize, **kwargs):
438440
"""
439441
Make iterator over chunks of samples

climada/engine/unsequa/calc_cost_benefit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ class CalcCostBenefit(Calc):
6868
('haz_input_var', 'ent_input_var', 'haz_fut_input_var', 'ent_fut_input_var')
6969
_metric_names : tuple(str)
7070
Names of the cost benefit output metrics
71-
('tot_climate_risk', 'benefit', 'cost_ben_ratio', 'imp_meas_present', 'imp_meas_future')
71+
('tot_climate_risk', 'benefit', 'cost_ben_ratio',
72+
'imp_meas_present', 'imp_meas_future')
73+
7274
"""
7375

7476
_input_var_names = (

0 commit comments

Comments
 (0)