Skip to content

Commit c8495ea

Browse files
committed
Removes global risk transfer for now
1 parent 1005965 commit c8495ea

File tree

2 files changed

+3
-195
lines changed

2 files changed

+3
-195
lines changed

climada/trajectories/impact_calc_strat.py

Lines changed: 2 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,7 @@
2121
2222
"""
2323

24-
import copy
2524
from abc import ABC, abstractmethod
26-
from typing import Any, Optional, Union
27-
28-
import numpy as np
29-
from scipy import sparse
3025

3126
from climada.engine.impact import Impact
3227
from climada.engine.impact_calc import ImpactCalc
@@ -56,9 +51,6 @@ def compute_impacts(
5651
exp: Exposures,
5752
haz: Hazard,
5853
vul: ImpactFuncSet,
59-
risk_transf_attach: Optional[float] = None,
60-
risk_transf_cover: Optional[float] = None,
61-
calc_residual: bool = True,
6254
) -> Impact:
6355
"""
6456
Calculates the total impact, including optional risk transfer application.
@@ -71,16 +63,6 @@ def compute_impacts(
7163
The hazard data (e.g., event intensity).
7264
vul : ImpactFuncSet
7365
The set of vulnerability functions.
74-
risk_transf_attach : float, optional
75-
The attachment point (deductible) for the global risk transfer mechanism.
76-
If None, default to 0.
77-
risk_transf_cover : float, optional
78-
The cover limit for the risk transfer mechanism. If None, the cover
79-
is assumed to be infinite (only the attachment applies).
80-
calc_residual : bool, default=True
81-
If True, the function returns the residual impact (after risk transfer).
82-
If False, it returns the transferred impact (the part covered by the
83-
risk transfer).
8466
8567
Returns
8668
-------
@@ -99,28 +81,7 @@ class ImpactCalcComputation(ImpactComputationStrategy):
9981
Default impact computation strategy using the core engine of climada.
10082
10183
This strategy first calculates the raw impact using the standard
102-
:class:`ImpactCalc` logic and then applies a global risk transfer mechanism.
103-
104-
This risk transfer is distinct and non-exclusive with the risk transfer
105-
defined through the Exposures object. While in the Exposures case, the transfer
106-
is defined on a per coordinate basis, this one is applied total impact on the whole region
107-
considered, and proportionally rescales the impacts per coordinate accordingly.
108-
109-
Notes
110-
-----
111-
The calculation is performed event-wise:
112-
113-
1. **Total Impact**: Calculate the total impact for each event
114-
(sum of impacts across all exposure points).
115-
2. **Transferred Risk per Event**: Defined as:
116-
$$\min(\max(0, \text{Total Impact} - \text{attachement}), \text{cover})$$
117-
3. **Residual Risk per Event**:
118-
$$\text{Total Impact} - \text{Transferred Risk per Event}$$
119-
4. **Adjustment**: The original impact per exposure point is scaled
120-
by the ratio of (Residual Risk / Total Impact) or
121-
(Transferred Risk / Total Impact) for that event.
122-
This ensures the risk transfer is shared proportionally among all
123-
impacted exposure points.
84+
:class:`ImpactCalc` logic.
12485
12586
"""
12687

@@ -129,9 +90,6 @@ def compute_impacts(
12990
exp: Exposures,
13091
haz: Hazard,
13192
vul: ImpactFuncSet,
132-
risk_transf_attach: Optional[float] = None,
133-
risk_transf_cover: Optional[float] = None,
134-
calc_residual: bool = False,
13593
) -> Impact:
13694
"""
13795
Calculates the impact and applies the "global" risk transfer mechanism.
@@ -144,22 +102,13 @@ def compute_impacts(
144102
The hazard data.
145103
vul : ImpactFuncSet
146104
The set of vulnerability functions.
147-
risk_transf_attach : float, optional
148-
The attachment point (deductible) for the risk transfer mechanism.
149-
risk_transf_cover : float, optional
150-
The cover limit for the risk transfer mechanism.
151-
calc_residual : bool, default=False
152-
If True, returns the residual impact. If False, returns the transferred impact.
153105
154106
Returns
155107
-------
156108
Impact
157-
The final impact object (either residual or transferred).
109+
The final impact object.
158110
"""
159111
impact = self.compute_impacts_pre_transfer(exp, haz, vul)
160-
self._apply_risk_transfer(
161-
impact, risk_transf_attach, risk_transf_cover, calc_residual
162-
)
163112
return impact
164113

165114
def compute_impacts_pre_transfer(
@@ -186,106 +135,3 @@ def compute_impacts_pre_transfer(
186135
An Impact object containing the raw, pre-transfer impact matrix.
187136
"""
188137
return ImpactCalc(exposures=exp, impfset=vul, hazard=haz).impact()
189-
190-
def _apply_risk_transfer(
191-
self,
192-
impact: Impact,
193-
risk_transf_attach: Optional[float],
194-
risk_transf_cover: Optional[float],
195-
calc_residual: bool,
196-
) -> None:
197-
"""
198-
Applies risk transfer logic and modifies the Impact object in-place.
199-
200-
Parameters
201-
----------
202-
impact : Impact
203-
The Impact object whose impact matrix will be modified.
204-
risk_transf_attach : float, optional
205-
The attachment point.
206-
risk_transf_cover : float, optional
207-
The cover limit.
208-
calc_residual : bool
209-
Determines whether to set the matrix to the residual or transferred impact.
210-
"""
211-
if risk_transf_attach is not None or risk_transf_cover is not None:
212-
impact.imp_mat = self.calculate_residual_or_risk_transfer_impact_matrix(
213-
impact.imp_mat, risk_transf_attach, risk_transf_cover, calc_residual
214-
)
215-
216-
def calculate_residual_or_risk_transfer_impact_matrix(
217-
self,
218-
imp_mat: sparse.csr_matrix,
219-
attachement: Optional[float],
220-
cover: Optional[float],
221-
calc_residual: bool,
222-
) -> sparse.csr_matrix:
223-
r"""
224-
Calculates either the residual or the risk transfer impact matrix
225-
based on a global risk transfer mechanism.
226-
227-
This function modifies the original impact matrix values proportionally
228-
based on the total event impact relative to the attachment and cover.
229-
230-
Parameters
231-
----------
232-
imp_mat : scipy.sparse.csr_matrix or object with .data
233-
The original impact matrix (events x exposure points).
234-
attachement : float, optional
235-
The attachment point (deductible).
236-
cover : float, optional
237-
The cover limit.
238-
calc_residual : bool
239-
If True, the function returns the residual impact matrix.
240-
If False, it returns the transferred risk impact matrix.
241-
242-
Returns
243-
-------
244-
scipy.sparse.csr_matrix or object with .data
245-
The adjusted impact matrix (residual or transferred).
246-
247-
Notes
248-
-----
249-
The calculation is performed event-wise:
250-
251-
1. **Total Impact**: Calculate the total impact for each event
252-
(sum of impacts across all exposure points).
253-
2. **Transferred Risk per Event**: Defined as:
254-
$$\min(\max(0, \text{Total Impact} - \text{attachement}), \text{cover})$$
255-
3. **Residual Risk per Event**:
256-
$$\text{Total Impact} - \text{Transferred Risk per Event}$$
257-
4. **Adjustment**: The original impact per exposure point is scaled
258-
by the ratio of (Residual Risk / Total Impact) or
259-
(Transferred Risk / Total Impact) for that event.
260-
This ensures the risk transfer is shared proportionally among all
261-
impacted exposure points.
262-
"""
263-
imp_mat = copy.deepcopy(imp_mat)
264-
# Calculate the total impact per event
265-
total_at_event = imp_mat.sum(axis=1).A1 # type: ignore
266-
# Risk layer at event
267-
attachement = 0 if attachement is None else attachement
268-
cover = np.inf if cover is None else cover
269-
transfer_at_event = np.minimum(
270-
np.maximum(total_at_event - attachement, 0), cover
271-
)
272-
residual_at_event = np.maximum(total_at_event - transfer_at_event, 0)
273-
274-
# Calculate either the residual or transfer impact matrix
275-
# Choose the denominator to rescale the impact values
276-
if calc_residual:
277-
numerator = residual_at_event
278-
else:
279-
numerator = transfer_at_event
280-
281-
rescale_impact_values = np.divide(
282-
numerator,
283-
total_at_event,
284-
out=np.zeros_like(numerator, dtype=float),
285-
where=total_at_event != 0,
286-
)
287-
288-
# The multiplication is broadcasted across the columns for each row
289-
result_matrix = imp_mat.multiply(rescale_impact_values[:, np.newaxis])
290-
291-
return result_matrix

climada/trajectories/test/test_impact_calc_strat.py

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,14 @@ def setUp(self):
4848
self.impact_calc_computation = ImpactCalcComputation()
4949

5050
@patch.object(ImpactCalcComputation, "compute_impacts_pre_transfer")
51-
@patch.object(ImpactCalcComputation, "_apply_risk_transfer")
52-
def test_compute_impacts(
53-
self, mock_apply_risk_transfer, mock_calculate_impacts_for_snapshots
54-
):
51+
def test_compute_impacts(self, mock_calculate_impacts_for_snapshots):
5552
mock_impacts = MagicMock(spec=Impact)
5653
mock_calculate_impacts_for_snapshots.return_value = mock_impacts
5754

5855
result = self.impact_calc_computation.compute_impacts(
5956
exp=self.mock_snapshot0.exposure,
6057
haz=self.mock_snapshot0.hazard,
6158
vul=self.mock_snapshot0.impfset,
62-
risk_transf_attach=0.1,
63-
risk_transf_cover=0.9,
64-
calc_residual=False,
6559
)
6660

6761
self.assertEqual(result, mock_impacts)
@@ -70,7 +64,6 @@ def test_compute_impacts(
7064
self.mock_snapshot0.hazard,
7165
self.mock_snapshot0.impfset,
7266
)
73-
mock_apply_risk_transfer.assert_called_once_with(mock_impacts, 0.1, 0.9, False)
7467

7568
def test_calculate_impacts_for_snapshots(self):
7669
mock_imp_E0H0 = MagicMock(spec=Impact)
@@ -88,37 +81,6 @@ def test_calculate_impacts_for_snapshots(self):
8881

8982
self.assertEqual(result, mock_imp_E0H0)
9083

91-
def test_apply_risk_transfer(self):
92-
mock_imp_E0H0 = MagicMock(spec=Impact)
93-
mock_imp_E0H0.imp_mat = MagicMock(spec=csr_matrix)
94-
mock_imp_resi = MagicMock(spec=csr_matrix)
95-
96-
with patch.object(
97-
self.impact_calc_computation,
98-
"calculate_residual_or_risk_transfer_impact_matrix",
99-
) as mock_calc_risk_transfer:
100-
mock_calc_risk_transfer.return_value = mock_imp_resi
101-
self.impact_calc_computation._apply_risk_transfer(
102-
mock_imp_E0H0, 0.1, 0.9, False
103-
)
104-
105-
self.assertIs(mock_imp_E0H0.imp_mat, mock_imp_resi)
106-
107-
def test_calculate_residual_or_risk_transfer_impact_matrix(self):
108-
imp_mat = MagicMock()
109-
imp_mat.sum.return_value.A1 = np.array([100, 200, 300])
110-
imp_mat.multiply.return_value = "rescaled_matrix"
111-
112-
result = self.impact_calc_computation.calculate_residual_or_risk_transfer_impact_matrix(
113-
imp_mat, 0.1, 0.9, True
114-
)
115-
self.assertEqual(result, "rescaled_matrix")
116-
117-
result = self.impact_calc_computation.calculate_residual_or_risk_transfer_impact_matrix(
118-
imp_mat, 0.1, 0.9, False
119-
)
120-
self.assertEqual(result, "rescaled_matrix")
121-
12284

12385
if __name__ == "__main__":
12486
TESTS = unittest.TestLoader().loadTestsFromTestCase(TestImpactCalcComputation)

0 commit comments

Comments
 (0)