Skip to content

Commit 357541a

Browse files
Merge branch 'develop' into calibrate-impact-functions
2 parents 645862a + 8c8ee96 commit 357541a

File tree

12 files changed

+138
-67
lines changed

12 files changed

+138
-67
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ Code freeze date: YYYY-MM-DD
1414

1515
### Changed
1616

17+
- Update `CONTRIBUTING.md` to better explain types of contributions to this repository [#797](https://github.com/CLIMADA-project/climada_python/pull/797)
18+
- The default tile layer in Exposures maps is not Stamen Terrain anymore, but [CartoDB Positron](https://github.com/CartoDB/basemap-styles). Affected methods are `climada.engine.Impact.plot_basemap_eai_exposure`,`climada.engine.Impact.plot_basemap_impact_exposure` and `climada.entity.Exposures.plot_basemap`. [#798](https://github.com/CLIMADA-project/climada_python/pull/798)
19+
1720
### Fixed
1821

22+
- Fix the dist_approx util function when used with method="geosphere" and log=True and points that are very close. [#792](https://github.com/CLIMADA-project/climada_python/pull/792)
23+
1924
### Deprecated
2025

2126
### Removed

CONTRIBUTING.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
11
# CLIMADA Contribution Guide
22

3-
Thank you for contributing to CLIMADA!
3+
We welcome any contribution to CLIMADA and want to express our thanks to everybody who contributes.
44

5-
## Overview
5+
## What Warrants a Contribution?
6+
7+
Anything!
8+
For orientation, these are some categories of possible contributions we can think of:
9+
10+
* **Technical problems and bugs:** Did you encounter a problem when using CLIMADA? Raise an [issue](https://github.com/CLIMADA-project/climada_python/issues) in our repository, providing a description or ideally a code replicating the error. Did you already find a solution to the problem? Please raise a pull request to help us resolve the issue!
11+
* **Documentation and Tutorial Updates:** Found a typo in the documentation? Is a tutorial lacking some information you find important? Simply fix a line, or add a paragraph. We are happy to incorporate you additions! Please raise a pull request!
12+
* **New Modules and Utility Functions:** Did you create a function or an entire module you find useful for your work? Maybe you are not the only one! Feel free to simply raise a pull request for functions that improve, e.g., plotting or data handling. As an entire module has to be carefully integrated into the framework, it might help if you talk to us first so we can design the module and plan the next steps. You can do that by raising an issue or starting a [discussion](https://github.com/CLIMADA-project/climada_python/discussions) on GitHub.
13+
14+
A good place to start a personal discussion is our monthly CLIMADA developers call.
15+
Please contact the [lead developers](https://wcr.ethz.ch/research/climada.html) if you want to join.
16+
17+
## Why Should You Contribute?
18+
19+
* You will be listed as author of the CLIMADA repository in the [AUTHORS](AUTHORS.md) file.
20+
* You will improve the quality of the CLIMADA software for you and for everybody else using it.
21+
* You will gain insights into scientific software development.
22+
23+
## Minimal Steps to Contribute
624

725
Before you start, please have a look at our [Developer Guide][devguide].
826

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ Please use the following logo if you are presenting results obtained with or thr
6161

6262
## Contributing
6363

64-
See the [Contribution Guide](CONTRIBUTING.md).
64+
We welcome any contribution to this repository, be it bugfixes and other code changes and additions, documentation improvements, or tutorial updates.
65+
66+
If you would like to contribute, please refer to our [Contribution Guide](CONTRIBUTING.md).
6567

6668
## Versioning
6769

climada/engine/impact.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ def plot_raster_eai_exposure(self, res=None, raster_res=None, save_tiff=None,
673673

674674
def plot_basemap_eai_exposure(self, mask=None, ignore_zero=False, pop_name=True,
675675
buffer=0.0, extend='neither', zoom=10,
676-
url=ctx.providers.Stamen.Terrain,
676+
url=ctx.providers.CartoDB.Positron,
677677
axis=None, **kwargs):
678678
"""Plot basemap expected impact of each exposure within a period of 1/frequency_unit.
679679
@@ -694,7 +694,7 @@ def plot_basemap_eai_exposure(self, mask=None, ignore_zero=False, pop_name=True,
694694
zoom : int, optional
695695
zoom coefficient used in the satellite image
696696
url : str, optional
697-
image source, e.g. ctx.providers.OpenStreetMap.Mapnik
697+
image source, default: ctx.providers.CartoDB.Positron
698698
axis : matplotlib.axes.Axes, optional
699699
axis to use
700700
kwargs : dict, optional
@@ -764,7 +764,7 @@ def plot_hexbin_impact_exposure(self, event_id=1, mask=None, ignore_zero=False,
764764

765765
def plot_basemap_impact_exposure(self, event_id=1, mask=None, ignore_zero=False,
766766
pop_name=True, buffer=0.0, extend='neither', zoom=10,
767-
url=ctx.providers.Stamen.Terrain,
767+
url=ctx.providers.CartoDB.Positron,
768768
axis=None, **kwargs):
769769
"""Plot basemap impact of an event at each exposure.
770770
Requires attribute imp_mat.
@@ -789,7 +789,7 @@ def plot_basemap_impact_exposure(self, event_id=1, mask=None, ignore_zero=False,
789789
zoom : int, optional
790790
zoom coefficient used in the satellite image
791791
url : str, optional
792-
image source, e.g. ctx.providers.OpenStreetMap.Mapnik
792+
image source, default: ctx.providers.CartoDB.Positron
793793
axis : matplotlib.axes.Axes, optional
794794
axis to use
795795
kwargs : dict, optional

climada/entity/exposures/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ def plot_raster(self, res=None, raster_res=None, save_tiff=None,
749749

750750
def plot_basemap(self, mask=None, ignore_zero=False, pop_name=True,
751751
buffer=0.0, extend='neither', zoom=10,
752-
url=None, axis=None, **kwargs):
752+
url=ctx.providers.CartoDB.Positron, axis=None, **kwargs):
753753
"""Scatter points over satellite image using contextily
754754
755755
Parameters
@@ -771,7 +771,7 @@ def plot_basemap(self, mask=None, ignore_zero=False, pop_name=True,
771771
zoom coefficient used in the satellite image
772772
url : Any, optional
773773
image source, e.g., ``ctx.providers.OpenStreetMap.Mapnik``.
774-
Default: ``ctx.providers.Stamen.Terrain``
774+
Default: ``ctx.providers.CartoDB.Positron``
775775
axis : matplotlib.axes._subplots.AxesSubplot, optional
776776
axis to use
777777
kwargs : optional

climada/hazard/test/test_trop_cyclone.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,22 +304,61 @@ def test_v_max_s_holland_2008_pass(self):
304304

305305
def test_holland_2010_pass(self):
306306
"""Test Holland et al. 2010 wind field model."""
307-
# test at centroids within and outside of radius of max wind
307+
# The parameter "x" is designed to be exactly 0.5 inside the radius of max wind (RMW) and
308+
# to increase or decrease linearly outside of it in radial direction.
309+
#
310+
# An increase (decrease) of "x" outside of the RMW is for cases where the max wind is very
311+
# high (low), but the RMW is still comparably large (small). This means, wind speeds need
312+
# to decay very sharply (only moderately) outside of the RMW to reach the low prescribed
313+
# peripheral wind speeds.
314+
#
315+
# The "hol_b" parameter tunes the meaning of a "comparably" large or small RMW.
308316
si_track = xr.Dataset({
309-
"rad": ("time", KM_TO_M * np.array([75, 40])),
310-
"vmax": ("time", [35.0, 40.0]),
311-
"hol_b": ("time", [1.80, 2.5]),
317+
# four test cases:
318+
# - low vmax, moderate RMW: x decreases moderately
319+
# - large hol_b: x decreases sharply
320+
# - very low vmax: x decreases so much, it needs to be clipped at 0
321+
# - large vmax, large RMW: x increases
322+
"rad": ("time", KM_TO_M * np.array([75, 75, 75, 90])),
323+
"vmax": ("time", [35.0, 35.0, 16.0, 90.0]),
324+
"hol_b": ("time", [1.75, 2.5, 1.9, 1.6]),
312325
})
313-
d_centr = KM_TO_M * np.array([[35, 75, 220], [30, 1000, 300]], dtype=float)
314-
close_centr = np.array([[True, True, True], [True, False, True]], dtype=bool)
326+
d_centr = KM_TO_M * np.array([
327+
# first column is for locations within the storm eye
328+
# second column is for locations at or close to the radius of max wind
329+
# third column is for locations outside the storm eye
330+
# fourth column is for locations exactly at the peripheral radius
331+
# fifth column is for locations outside the peripheral radius
332+
[0., 75, 220, 300, 490],
333+
[30, 74, 170, 300, 501],
334+
[21, 76, 230, 300, 431],
335+
[32, 91, 270, 300, 452],
336+
], dtype=float)
337+
close_centr = np.array([
338+
# note that we set one of these to "False" for testing
339+
[True, True, True, True, True],
340+
[True, True, True, True, False],
341+
[True, True, True, True, True],
342+
[True, True, True, True, True],
343+
], dtype=bool)
315344
hol_x = _x_holland_2010(si_track, d_centr, close_centr)
316-
np.testing.assert_array_almost_equal(
317-
hol_x, [[0.5, 0.5, 0.47273], [0.5, 0, 0.211602]])
345+
np.testing.assert_array_almost_equal(hol_x, [
346+
[0.5, 0.500000, 0.485077, 0.476844, 0.457291],
347+
[0.5, 0.500000, 0.410997, 0.289203, 0.000000],
348+
[0.5, 0.497620, 0.131072, 0.000000, 0.000000],
349+
[0.5, 0.505022, 1.403952, 1.554611, 2.317948],
350+
])
318351

319-
# test exactly at radius of maximum wind (35 m/s) and at peripheral radius (17 m/s)
320352
v_ang_norm = _stat_holland_2010(si_track, d_centr, close_centr, hol_x)
321-
np.testing.assert_array_almost_equal(v_ang_norm,
322-
[[15.957853, 35.0, 20.99411], [33.854826, 0, 17.0]])
353+
np.testing.assert_array_almost_equal(v_ang_norm, [
354+
# first column: converge to 0 when approaching storm eye
355+
# second column: vmax at RMW
356+
# fourth column: peripheral speed (17 m/s) at peripheral radius (unless x is clipped!)
357+
[0.0000000, 35.000000, 21.181497, 17.00000, 12.103461],
358+
[1.2964800, 34.990037, 21.593755, 17.00000, 0.0000000],
359+
[0.3219518, 15.997500, 13.585498, 16.00000, 16.000000],
360+
[24.823469, 89.992938, 24.381965, 17.00000, 1.9292020],
361+
])
323362

324363
def test_stat_holland_1980(self):
325364
"""Test _stat_holland_1980 function. Compare to MATLAB reference."""

climada/hazard/trop_cyclone.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1424,7 +1424,13 @@ def _x_holland_2010(
14241424
# linearly interpolate between max exponent and peripheral exponent
14251425
x_max = 0.5
14261426
hol_x[close_centr] = x_max + np.fmax(0, d_centr - r_max) * (x_n - x_max) / (r_n - r_max)
1427-
hol_x[close_centr] = np.clip(hol_x[close_centr], 0.0, 0.5)
1427+
1428+
# Negative hol_x values appear when v_max_s is very close to or even lower than v_n (which
1429+
# should never happen in theory). In those cases, wind speeds might decrease outside of the eye
1430+
# wall and increase again towards the peripheral radius (which is actually unphysical).
1431+
# We clip hol_x to 0, otherwise wind speeds keep increasing indefinitely away from the eye:
1432+
hol_x[close_centr] = np.fmax(hol_x[close_centr], 0.0)
1433+
14281434
return hol_x
14291435

14301436
def _stat_holland_2010(

climada/test/test_plot.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,7 @@ def test_ctx_osm_pass(self):
162162
myexp.gdf['value'] = np.array([1, 1, 1])
163163
myexp.check()
164164

165-
try:
166-
myexp.plot_basemap(url=ctx.providers.OpenStreetMap.Mapnik)
167-
except urllib.error.HTTPError:
168-
self.assertEqual(1, 0)
165+
myexp.plot_basemap(url=ctx.providers.OpenStreetMap.Mapnik)
169166

170167
def test_disc_rates(self):
171168
"""Test plot function of discount rates."""

climada/util/api_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ def _divide_straight_from_multi(properties):
341341
elif isinstance(_v, list):
342342
multis[k] = _v
343343
else:
344-
raise ValueError("properties must be a string or a list of strings")
344+
raise ValueError("the value of a property must be a string or a list of strings")
345345
return straights, multis
346346

347347
@staticmethod

climada/util/coordinates.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,13 @@ def dist_approx(lat1, lon1, lat2, lon2, log=False, normalize=True,
358358
if log:
359359
vec1, vbasis = latlon_to_geosph_vector(lat1, lon1, rad=True, basis=True)
360360
vec2 = latlon_to_geosph_vector(lat2, lon2, rad=True)
361-
scal = 1 - 2 * hav
362-
fact = dist / np.fmax(np.spacing(1), np.sqrt(1 - scal**2))
363-
vtan = fact[..., None] * (vec2[:, None, :] - scal[..., None] * vec1[:, :, None])
361+
vtan = vec2[:, None, :] - (1 - 2 * hav[..., None]) * vec1[:, :, None]
364362
vtan = np.einsum('nkli,nkji->nklj', vtan, vbasis)
363+
# faster version of `vtan_norm = np.linalg.norm(vtan, axis=-1)`
364+
vtan_norm = np.sqrt(np.einsum("...l,...l->...", vtan, vtan))
365+
# for consistency, set dist to 0 if vtan is 0
366+
dist[vtan_norm < np.spacing(1)] = 0
367+
vtan *= dist[..., None] / np.fmax(np.spacing(1), vtan_norm[..., None])
365368
else:
366369
raise KeyError("Unknown distance approximation method: %s" % method)
367370
return (dist, vtan) if log else dist

0 commit comments

Comments
 (0)