Skip to content

Commit

Permalink
Merge branch 'feature/exposures_crs' into feature/split_hazard_module
Browse files Browse the repository at this point in the history
  • Loading branch information
emanuel-schmid committed May 8, 2024
2 parents 4c84ed1 + ac03762 commit 6f551ff
Show file tree
Hide file tree
Showing 21 changed files with 569 additions and 638 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Code freeze date: YYYY-MM-DD

### Changed

- Remove content tables and make minor improvements (fix typos and readability) in
CLIMADA tutorials. [#872](https://github.com/CLIMADA-project/climada_python/pull/872)
- Centroids complete overhaul. Most function should be backward compatible. Internal data is stored in a geodataframe attribute. Raster are now stored as points, and the meta attribute is removed. Several methds were deprecated or removed. [#787](https://github.com/CLIMADA-project/climada_python/pull/787)
- Improved error messages produced by `ImpactCalc.impact()` in case impact function in the exposures is not found in impf_set [#863](https://github.com/CLIMADA-project/climada_python/pull/863)
- Changed module structure: `climada.hazard.Hazard` has been split into the modules `base`, `io` and `plot` [#871](https://github.com/CLIMADA-project/climada_python/pull/871)
Expand All @@ -22,6 +24,7 @@ Code freeze date: YYYY-MM-DD

### Added

- Generic s-shaped impact function via `ImpactFunc.from_poly_s_shape` [#878](https://github.com/CLIMADA-project/climada_python/pull/878)
- climada.hazard.centroids.centr.Centroids.get_area_pixel
- climada.hazard.centroids.centr.Centroids.get_dist_coast
- climada.hazard.centroids.centr.Centroids.get_elevation
Expand Down
98 changes: 91 additions & 7 deletions climada/entity/impact_funcs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,9 @@ def from_step_impf(

""" Step function type impact function.
By default, everything is destroyed above the step.
By default, the impact is 100% above the step.
Useful for high resolution modelling.
This method modifies self (climada.entity.impact_funcs instance)
by assigning an id, intensity, mdd and paa to the impact function.
Parameters
----------
intensity: tuple(float, float, float)
Expand Down Expand Up @@ -226,12 +223,14 @@ def from_sigmoid_impf(
haz_type: str,
impf_id: int = 1,
**kwargs):
"""Sigmoid type impact function hinging on three parameter.
r"""Sigmoid type impact function hinging on three parameter.
This type of impact function is very flexible for any sort of study,
hazard and resolution. The sigmoid is defined as:
.. math:: f(x) = \\frac{L}{1+exp^{-k(x-x0)}}
.. math::
f(x) = \frac{L}{1+e^{-k(x-x0)}}
For more information: https://en.wikipedia.org/wiki/Logistic_function
Expand All @@ -240,7 +239,7 @@ def from_sigmoid_impf(
Parameters
----------
intensity: tuple(float, float, float)
intensity : tuple(float, float, float)
tuple of 3 intensity numbers along np.arange(min, max, step)
L : float
"top" of sigmoid
Expand Down Expand Up @@ -273,3 +272,88 @@ def set_sigmoid_impf(self, *args, **kwargs):
LOGGER.warning("The use of ImpactFunc.set_sigmoid_impf is deprecated."
"Use ImpactFunc.from_sigmoid_impf instead.")
self.__dict__ = ImpactFunc.from_sigmoid_impf(*args, **kwargs).__dict__

@classmethod
def from_poly_s_shape(
cls,
intensity: tuple[float, float, int],
threshold: float,
half_point: float,
scale: float,
exponent: float,
haz_type: str,
impf_id: int = 1,
**kwargs):
r"""S-shape polynomial impact function hinging on four parameter.
.. math::
f(I) = \frac{\textrm{luk}(I)^{\textrm{exponent}}}{
1 + \textrm{luk}(I)^{\textrm{exponent}}
}
\cdot \textrm{scale} \\
\textrm{luk}(I) = \frac{\max[I - \textrm{threshold}, 0]}{
\textrm{half_point} - \textrm{threshold}
}
This function is inspired by Emanuel et al. (2011)
https://doi.org/10.1175/WCAS-D-11-00007.1
This method only specifies mdd, and paa = 1 for all intensities.
Parameters
----------
intensity : tuple(float, float, float)
tuple of 3 intensity numbers along np.linsapce(min, max, num)
threshold : float
Intensity threshold below which there is no impact.
In general choose threshold > 0 for computational efficiency
of impacts.
half_point : float
Intensity at which 50% of maximum impact is expected.
If half_point <= threshold, mdd = 0 (and f(I)=0) for all
intensities.
scale : float
Multiplicative factor for the whole function. Typically,
this sets the maximum value at large intensities.
exponent: float
Exponent of the polynomial. Value must be exponent >= 0.
Emanuel et al. (2011) uses the value 3.
haz_type: str
Reference string for the hazard (e.g., 'TC', 'RF', 'WS', ...)
impf_id : int, optional, default=1
Impact function id
kwargs :
keyword arguments passed to ImpactFunc()
Raises
------
ValueError : if exponent <= 0
Returns
-------
impf : climada.entity.impact_funcs.ImpactFunc
s-shaped polynomial impact function
"""
if exponent < 0:
raise ValueError('Exponent value must larger than 0')

inten = np.linspace(*intensity)

if threshold >= half_point:
mdd = np.zeros_like(inten)
else:
luk = (inten - threshold) / (half_point - threshold)
luk[luk < 0] = 0
mdd = scale * luk**exponent / (1 + luk**exponent)
paa = np.ones_like(inten)

impf = cls(
haz_type=haz_type,
id=impf_id,
intensity=inten,
paa=paa,
mdd=mdd,
**kwargs
)
return impf
54 changes: 53 additions & 1 deletion climada/entity/impact_funcs/test/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def test_from_step(self):
self.assertEqual(imp_fun.haz_type, 'TC')
self.assertEqual(imp_fun.id, 2)


def test_from_sigmoid(self):
"""Check default impact function: sigmoid function"""
inten = (0, 100, 5)
Expand All @@ -60,6 +59,59 @@ def test_from_sigmoid(self):
self.assertEqual(imp_fun.haz_type, 'RF')
self.assertEqual(imp_fun.id, 2)

def test_from_poly_s_shape(self):
"""Check default impact function: polynomial s-shape"""

haz_type = 'RF'
threshold = 0.2
half_point = 1
scale = 0.8
exponent = 4
impf_id = 2
unit = 'm'
intensity = (0, 5, 5)

def test_aux_vars(impf):
self.assertTrue(np.array_equal(impf.paa, np.ones(5)))
self.assertTrue(np.array_equal(impf.intensity, np.linspace(0, 5, 5)))
self.assertEqual(impf.haz_type, haz_type)
self.assertEqual(impf.id, impf_id)
self.assertEqual(impf.intensity_unit, unit)

impf = ImpactFunc.from_poly_s_shape(
intensity=intensity, threshold=threshold, half_point=half_point, scale=scale,
exponent=exponent, haz_type=haz_type, impf_id=impf_id, intensity_unit=unit
)
# True value can easily be computed with a calculator
correct_mdd = np.array([0, 0.59836395, 0.78845941, 0.79794213, 0.79938319])
np.testing.assert_array_almost_equal(impf.mdd, correct_mdd)
test_aux_vars(impf)

# If threshold > half_point, mdd should all be 0
impf = ImpactFunc.from_poly_s_shape(
intensity=intensity, threshold=half_point*2, half_point=half_point, scale=scale,
exponent=exponent, haz_type=haz_type, impf_id=impf_id, intensity_unit=unit
)
np.testing.assert_array_almost_equal(impf.mdd, np.zeros(5))
test_aux_vars(impf)

# If exponent = 0, mdd should be constant
impf = ImpactFunc.from_poly_s_shape(
intensity=intensity, threshold=threshold, half_point=half_point, scale=scale,
exponent=0, haz_type=haz_type, impf_id=impf_id, intensity_unit=unit
)
np.testing.assert_array_almost_equal(impf.mdd, np.ones(5) * scale / 2)
test_aux_vars(impf)

# If exponent < 0, raise error.
with self.assertRaisesRegex(ValueError, "Exponent value"):
ImpactFunc.from_poly_s_shape(
intensity=intensity, threshold=half_point,
half_point=half_point, scale=scale,
exponent=-1, haz_type=haz_type,
impf_id=impf_id, intensity_unit=unit
)

# Execute Tests
if __name__ == "__main__":
TESTS = unittest.TestLoader().loadTestsFromTestCase(TestInterpolation)
Expand Down
75 changes: 0 additions & 75 deletions doc/guide/Guide_CLIMADA_Tutorial.ipynb

Large diffs are not rendered by default.

39 changes: 19 additions & 20 deletions doc/guide/Guide_Introduction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
"According to the IPCC [[1]](#1), natural risks emerge through the\n",
"interplay of climate and weather-related hazards, the exposure of goods\n",
"or people to this hazard, and the specific vulnerability of exposed\n",
"people, infrastructure and environment. The unit chosen to measure risk\n",
"has to be the most relevant one in a specific decision problem, not\n",
"necessarily monetary units. Wildfire hazard might be measured by burned\n",
"area, exposure by population or replacement value of homes and hence\n",
"risk might be expressed as number of affected people in the context of\n",
"evacuation, or repair cost of buildings in the context of property\n",
"insurance.\n",
"people, infrastructure and environment. \n",
"\n",
"The unit of measurement for risk in CLIMADA is selected based on its relevance\n",
"to the specific decision-making context and is not limited to monetary units\n",
"alone. For instance, wildfire risk may be quantified by the burned area\n",
"(hazard) and the exposure could be measured by the population density or the\n",
"replacement value of homes. Consequently, risk could be expressed in terms of\n",
"the number of people affected for evacuation planning, or the cost of repairs\n",
"for property insurance purposes.\n",
"\n",
"Risk has been defined by the International Organization for\n",
"Standardization as the \"effect of uncertainty on objectives\" as the\n",
Expand All @@ -36,13 +38,16 @@
"$$\\text{severity} = F(\\text{hazard intensity}, \\text{exposure}, \\text{vulnerability}) = \\text{exposure} * f_{\\text{imp}}(\\text{hazard intensity})$$\n",
"\n",
"where $f_{\\text{imp}}$ is the impact function which parametrizes to what extent\n",
"an exposure will be affected by a specific hazard. While 'vulnerability\n",
"function' is broadly used in the modelers community, we refer to it as\n",
"'impact function' to explicitly include the option of opportunities\n",
"(i.e. negative damages). Using this approach, CLIMADA constitutes a\n",
"platform to analyse risks of different hazard types in a globally\n",
"consistent fashion at different resolution levels, at scales from\n",
"multiple kilometres down to meters, depending on the purpose.\n",
"an exposure will be affected by a specific hazard. **While the term 'vulnerability\n",
"function' is broadly used in the modelers community, we adopt the broader term \n",
"'impact function'. Impact functions can be vulnerability functions or structural \n",
"damage functions, but could also be productivity functions or warning levels. This \n",
"definition also explicitly includes the option of opportunities (i.e. negative damages).**\n",
"\n",
"Using this approach, CLIMADA constitutes a platform to analyse risks of different\n",
"hazard types in a globally consistent fashion at different resolution levels, \n",
"at scales from multiple kilometres down to meters, tailored to the specific \n",
"requirements of the analysis.\n",
"\n",
"\n",
"<br/><br/>\n",
Expand All @@ -61,12 +66,6 @@
" Cambridge University Press, United Kingdom and New York, NY,\n",
" USA., 2014."
]
},
{
"cell_type": "markdown",
"id": "503ec4a7",
"metadata": {},
"source": []
}
],
"metadata": {
Expand Down
2 changes: 1 addition & 1 deletion doc/guide/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ JupyterLab

.. code-block:: shell
mamba env activate climada_env
mamba activate climada_env
jupyter-lab
JupyterLab will open in a new window of your default browser.
Expand Down
16 changes: 0 additions & 16 deletions doc/tutorial/0_intro_python.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,6 @@
"Most of the examples come from the official Python tutorial: https://docs.python.org/3/tutorial/"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Contents\n",
"\n",
"- [Numbers and Strings](#Numbers-and-Strings)\n",
"- [Lists](#Lists)\n",
"- [Tuples](#Tuples)\n",
"- [Sets](#Sets)\n",
"- [Dictonaries](#Dictionaries)\n",
"- [Functions](#Functions)\n",
"- [Objects](#Objects)"
]
},
{
"attachments": {},
"cell_type": "markdown",
Expand Down
28 changes: 2 additions & 26 deletions doc/tutorial/1_main_climada.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# CLIMADA overview\n",
"\n",
"## Contents\n",
"\n",
"- [Introduction](#Introduction)\n",
" - [What is CLIMADA?](#What-is-CLIMADA)\n",
" - [This tutorial](#This-tutorial)\n",
" - [Resources beyond this tutorial](#Resources-beyond-this-tutorial)\n",
"- [CLIMADA features](#CLIMADA-features)\n",
" - [CLIMADA classes](#CLIMADA-classes)\n",
"- [Tutorial: an example risk assessment](#Tutorial--an-example-risk-assessment)\n",
" - [Hazard](#Hazard)\n",
" - [Storm tracks](#Storm-tracks)\n",
" - [Centroids](#Centroids)\n",
" - [Hazard footprint](#Hazard-footprint)\n",
" - [Entity](#Entity)\n",
" - [Exposures](#Exposures)\n",
" - [Impact functions](#Impact-functions)\n",
" - [Adaptation measures](#Adaptation-measures)\n",
" - [Discount rates](#Discount-rates)\n",
" - [Engine](#Engine)\n",
" - [Impact](#Impact)\n",
" - [Adaptation options appraisal](#Adaptation-options-appraisal)\n",
" "
"# CLIMADA overview"
]
},
{
Expand Down Expand Up @@ -956,8 +933,7 @@
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
],
"outputs": [],
"source": [
"from climada.entity import Entity\n",
"\n",
Expand Down
23 changes: 5 additions & 18 deletions doc/tutorial/climada_engine_CostBenefit.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@
"# END-TO-END COST BENEFIT CALCULATION"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Contents\n",
"\n",
"- [Introduction](#Introduction)\n",
"- [What is a cost-benefit?](#What-is-a-cost-benefit?)\n",
"- [CostBenefit class data structure](#CostBenefit-class-data-structure)\n",
"- [Detailed CostBenefit calculation: LitPop + TropCyclone](#Detailed-CostBenefit-calculation:-LitPop-+-TropCyclone)\n",
"- [Conclusion](#Conclusion)"
]
},
{
"attachments": {},
"cell_type": "markdown",
Expand Down Expand Up @@ -87,7 +74,7 @@
"We can modify the benefit part of cost-benefit to reflect this. CLIMADA doesn't assume that the user will have explicit hazard and impact objects for every year in the study period, and so interpolates between the impacts at the start and the end of the period of interest. If we're evaluating between years $T_0$, usually close to the present, and $T_1$ in the future, then we can say:\n",
"\n",
"$$\n",
"\\text{benefit} = \\sum_{t = T_0}^{T_1} \\alpha(t) \\bigg{(} \\text{AAI with measures}_{T_1} - \\text{AAI with measures}_{T_0} \\bigg{)} - N * \\text{AAI without measure}_{T_0}\n",
"\\text{benefit} = \\sum_{t = T_0}^{T_1} \\alpha(t) \\bigg( \\text{AAI with measures}_{T_1} - \\text{AAI with measures}_{T_0} \\bigg) - N * \\text{AAI without measure}_{T_0}\n",
"$$\n",
"\n",
"Where $\\alpha(t)$ is a function of the year $t$ describing the interpolation of hazard and exposure values between $T_0$ and $T_1$. The function returns values in the range $[0, 1]$, usually with $\\alpha(T_0) = 0$ and $\\alpha(T_0) = 1$.\n",
Expand Down Expand Up @@ -270,12 +257,12 @@
"\n",
"client = Client()\n",
"future_year = 2080\n",
"haz_present = client.get_hazard('tropical_cyclone', \n",
" properties={'country_name': 'Haiti', \n",
"haz_present = client.get_hazard('tropical_cyclone',\n",
" properties={'country_name': 'Haiti',\n",
" 'climate_scenario': 'historical',\n",
" 'nb_synth_tracks':'10'})\n",
"haz_future = client.get_hazard('tropical_cyclone', \n",
" properties={'country_name': 'Haiti', \n",
"haz_future = client.get_hazard('tropical_cyclone',\n",
" properties={'country_name': 'Haiti',\n",
" 'climate_scenario': 'rcp60',\n",
" 'ref_year': str(future_year),\n",
" 'nb_synth_tracks':'10'})\n"
Expand Down
Loading

0 comments on commit 6f551ff

Please sign in to comment.