Skip to content

Commit 69a555e

Browse files
authored
Merge pull request #1199 from CLIMADA-project/feature/impact-computation-strategies
Risk Trajectory Split 3 : Impact Computation Strategies
2 parents 710a263 + 513605b commit 69a555e

4 files changed

Lines changed: 219 additions & 0 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
This file is part of CLIMADA.
3+
4+
Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.
5+
6+
CLIMADA is free software: you can redistribute it and/or modify it under the
7+
terms of the GNU General Public License as published by the Free
8+
Software Foundation, version 3.
9+
10+
CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
11+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12+
PARTICULAR PURPOSE. See the GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License along
15+
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.
16+
17+
---
18+
19+
This modules implements the impact computation strategy objects for risk
20+
trajectories.
21+
22+
"""
23+
24+
from abc import ABC, abstractmethod
25+
26+
from climada.engine.impact import Impact
27+
from climada.engine.impact_calc import ImpactCalc
28+
from climada.entity.exposures.base import Exposures
29+
from climada.entity.impact_funcs.impact_func_set import ImpactFuncSet
30+
from climada.hazard.base import Hazard
31+
32+
__all__ = ["ImpactCalcComputation"]
33+
34+
35+
# The following is acceptable.
36+
# We design a pattern, and currently it requires only to
37+
# define the compute_impacts method.
38+
# pylint: disable=too-few-public-methods
39+
class ImpactComputationStrategy(ABC):
40+
"""
41+
Interface for impact computation strategies.
42+
43+
This abstract class defines the contract for all concrete strategies
44+
responsible for calculating and optionally modifying with a risk transfer,
45+
the impact computation, based on a set of inputs (exposure, hazard, vulnerability).
46+
47+
It revolves around a `compute_impacts()` method that takes as arguments
48+
the three dimensions of risk (exposure, hazard, vulnerability) and return an
49+
Impact object.
50+
"""
51+
52+
@abstractmethod
53+
def compute_impacts(
54+
self,
55+
exp: Exposures,
56+
haz: Hazard,
57+
vul: ImpactFuncSet,
58+
) -> Impact:
59+
"""
60+
Calculates the total impact, including optional risk transfer application.
61+
62+
Parameters
63+
----------
64+
exp : Exposures
65+
The exposure data.
66+
haz : Hazard
67+
The hazard data (e.g., event intensity).
68+
vul : ImpactFuncSet
69+
The set of vulnerability functions.
70+
71+
Returns
72+
-------
73+
Impact
74+
An object containing the computed total impact matrix and metrics.
75+
76+
See Also
77+
--------
78+
ImpactCalcComputation : The default implementation of this interface.
79+
"""
80+
81+
82+
class ImpactCalcComputation(ImpactComputationStrategy):
83+
r"""
84+
Default impact computation strategy using the core engine of climada.
85+
86+
This strategy first calculates the raw impact using the standard
87+
:class:`ImpactCalc` logic.
88+
89+
"""
90+
91+
def compute_impacts(
92+
self,
93+
exp: Exposures,
94+
haz: Hazard,
95+
vul: ImpactFuncSet,
96+
) -> Impact:
97+
"""
98+
Calculates the impact.
99+
100+
Parameters
101+
----------
102+
exp : Exposures
103+
The exposure data.
104+
haz : Hazard
105+
The hazard data.
106+
vul : ImpactFuncSet
107+
The set of vulnerability functions.
108+
109+
Returns
110+
-------
111+
Impact
112+
The final impact object.
113+
"""
114+
return ImpactCalc(exposures=exp, impfset=vul, hazard=haz).impact()
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
This file is part of CLIMADA.
3+
4+
Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.
5+
6+
CLIMADA is free software: you can redistribute it and/or modify it under the
7+
terms of the GNU General Public License as published by the Free
8+
Software Foundation, version 3.
9+
10+
CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
11+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12+
PARTICULAR PURPOSE. See the GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License along
15+
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.
16+
17+
---
18+
19+
Tests for impact_calc_strat
20+
21+
"""
22+
23+
from unittest.mock import MagicMock, patch
24+
25+
import pytest
26+
27+
from climada.engine import Impact
28+
from climada.entity import ImpactFuncSet
29+
from climada.entity.exposures import Exposures
30+
from climada.hazard import Hazard
31+
from climada.trajectories import Snapshot
32+
from climada.trajectories.impact_calc_strat import (
33+
ImpactCalcComputation,
34+
ImpactComputationStrategy,
35+
)
36+
37+
# --- Fixtures ---
38+
39+
40+
@pytest.fixture
41+
def mock_snapshot():
42+
"""Provides a snapshot with mocked exposure, hazard, and impact functions."""
43+
snap = MagicMock(spec=Snapshot)
44+
snap.exposure = MagicMock(spec=Exposures)
45+
snap.hazard = MagicMock(spec=Hazard)
46+
snap.impfset = MagicMock(spec=ImpactFuncSet)
47+
return snap
48+
49+
50+
@pytest.fixture
51+
def strategy():
52+
"""Provides an instance of the ImpactCalcComputation strategy."""
53+
return ImpactCalcComputation()
54+
55+
56+
# --- Tests ---
57+
def test_interface_compliance(strategy):
58+
"""Ensure the class correctly inherits from the Abstract Base Class."""
59+
assert isinstance(strategy, ImpactComputationStrategy)
60+
assert isinstance(strategy, ImpactCalcComputation)
61+
62+
63+
def test_compute_impacts(strategy, mock_snapshot):
64+
"""Test that compute_impacts calls the pre-transfer method correctly."""
65+
mock_impacts = MagicMock(spec=Impact)
66+
67+
# We patch the ImpactCalc within trajectories
68+
with patch("climada.trajectories.impact_calc_strat.ImpactCalc") as mock_ImpactCalc:
69+
mock_ImpactCalc.return_value.impact.return_value = mock_impacts
70+
result = strategy.compute_impacts(
71+
exp=mock_snapshot.exposure,
72+
haz=mock_snapshot.hazard,
73+
vul=mock_snapshot.impfset,
74+
)
75+
mock_ImpactCalc.assert_called_once_with(
76+
exposures=mock_snapshot.exposure,
77+
impfset=mock_snapshot.impfset,
78+
hazard=mock_snapshot.hazard,
79+
)
80+
mock_ImpactCalc.return_value.impact.assert_called_once()
81+
assert result == mock_impacts
82+
83+
84+
def test_cannot_instantiate_abstract_base_class():
85+
"""Ensure ImpactComputationStrategy cannot be instantiated directly."""
86+
with pytest.raises(TypeError, match="Can't instantiate abstract class"):
87+
ImpactComputationStrategy() # type: ignore
88+
89+
90+
@pytest.mark.parametrize("invalid_input", [None, 123, "string"])
91+
def test_compute_impacts_type_errors(strategy, invalid_input):
92+
"""
93+
Smoke test: Ensure that if ImpactCalc raises errors due to bad input,
94+
the strategy correctly propagates them.
95+
"""
96+
with pytest.raises(AttributeError):
97+
strategy.compute_impacts(invalid_input, invalid_input, invalid_input)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
climada\.trajectories\.impact_calc_strat module
2+
----------------------------------------
3+
4+
.. automodule:: climada.trajectories.impact_calc_strat
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

doc/api/climada/climada.trajectories.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ climada\.trajectories module
55
.. toctree::
66

77
climada.trajectories.snapshot
8+
climada.trajectories.impact_calc_strat
89
climada.trajectories.interpolation

0 commit comments

Comments
 (0)