Skip to content

Commit 9ca3940

Browse files
committed
Merge branch 'feature/static-risk-traj' into feature/interpolated-trajectories
2 parents 402b173 + e55a481 commit 9ca3940

10 files changed

Lines changed: 314 additions & 446 deletions

File tree

climada/hazard/test/test_xarray.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import numpy as np
2929
import xarray as xr
3030
from pyproj import CRS
31-
from scipy.sparse import csr_matrix
31+
from scipy.sparse import csr_array, csr_matrix
3232

3333
from climada.hazard.base import Hazard
3434
from climada.util.constants import DEF_CRS
@@ -104,8 +104,8 @@ def _assert_default_types(self, hazard):
104104
self.assertIsInstance(hazard.event_id, np.ndarray)
105105
self.assertIsInstance(hazard.event_name, list)
106106
self.assertIsInstance(hazard.frequency, np.ndarray)
107-
self.assertIsInstance(hazard.intensity, csr_matrix)
108-
self.assertIsInstance(hazard.fraction, csr_matrix)
107+
self.assertIsInstance(hazard.intensity, csr_matrix | csr_array)
108+
self.assertIsInstance(hazard.fraction, csr_matrix | csr_array)
109109
self.assertIsInstance(hazard.date, np.ndarray)
110110

111111
def test_load_path(self):
@@ -149,8 +149,11 @@ def _load_and_assert(**kwargs):
149149
def test_type_error(self):
150150
"""Calling 'from_xarray_raster' with wrong data type should throw"""
151151
# Passing a DataArray
152-
with xr.open_dataset(self.netcdf_path) as dset, self.assertRaisesRegex(
153-
TypeError, "This method only supports passing xr.Dataset"
152+
with (
153+
xr.open_dataset(self.netcdf_path) as dset,
154+
self.assertRaisesRegex(
155+
TypeError, "This method only supports passing xr.Dataset"
156+
),
154157
):
155158
Hazard.from_xarray_raster(dset["intensity"], "", "")
156159

climada/hazard/xarray.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _to_csr_matrix(array: xr.DataArray) -> sparse.csr_matrix:
5858
output_dtypes=[array.dtype],
5959
)
6060
sparse_coo = array.compute().data # Load into memory
61-
return sparse_coo.tocsr() # Convert sparse.COO to scipy.sparse.csr_matrix
61+
return sparse_coo.tocsr() # Convert sparse.COO to scipy.sparse.csr_array
6262

6363

6464
# Define accessors for xarray DataArrays

climada/trajectories/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
from .static_trajectory import StaticRiskTrajectory
2828

2929
__all__ = [
30+
"AllLinearStrategy",
31+
"ExponentialExposureStrategy",
3032
"Snapshot",
3133
"StaticRiskTrajectory",
3234
"InterpolatedRiskTrajectory",
33-
"AllLinearStrategy",
34-
"ExponentialExposureStrategy",
3535
]

climada/trajectories/interpolation.py

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@
3434
__all__ = [
3535
"AllLinearStrategy",
3636
"ExponentialExposureStrategy",
37-
"linear_interp_arrays",
38-
"linear_interp_imp_mat",
39-
"exponential_interp_arrays",
40-
"exponential_interp_imp_mat",
37+
"linear_convex_combination",
38+
"linear_interp_matrix_elemwise",
39+
"exponential_convex_combination",
40+
"exponential_interp_matrix_elemwise",
4141
]
4242

4343

44-
def linear_interp_imp_mat(
44+
def linear_interp_matrix_elemwise(
4545
mat_start: sparse.csr_matrix,
4646
mat_end: sparse.csr_matrix,
4747
number_of_interpolation_points: int,
@@ -85,7 +85,7 @@ def linear_interp_imp_mat(
8585
]
8686

8787

88-
def exponential_interp_imp_mat(
88+
def exponential_interp_matrix_elemwise(
8989
mat_start: sparse.csr_matrix,
9090
mat_end: sparse.csr_matrix,
9191
number_of_interpolation_points: int,
@@ -148,9 +148,10 @@ def exponential_interp_imp_mat(
148148
return res
149149

150150

151-
def linear_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarray:
151+
def linear_convex_combination(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarray:
152152
r"""
153-
Performs linear interpolation between two NumPy arrays over their first dimension.
153+
Performs a linear convex combination between two n x m NumPy arrays over their
154+
first dimension (n rows).
154155
155156
This function interpolates each metric (column) linearly across the time steps
156157
(rows), including both the start and end states.
@@ -175,6 +176,13 @@ def linear_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarr
175176
ValueError
176177
If `arr_start` and `arr_end` do not have the same shape.
177178
179+
Example
180+
--------
181+
>>> arr_start = [ [ 1, 1], [1, 2], [10, 20] ]
182+
>>> arr_end = [ [2, 2], [5, 6], [10, 30] ]
183+
>>> linear_interp_arrays(arr_start, arr_end)
184+
>>> [[1, 1], [3, 4], [10, 30]]
185+
178186
Notes
179187
-----
180188
The interpolation is performed element-wise along the first dimension
@@ -198,13 +206,14 @@ def linear_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarr
198206
return np.multiply(arr_start, prop0) + np.multiply(arr_end, prop1)
199207

200208

201-
def exponential_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarray:
209+
def exponential_convex_combination(
210+
arr_start: np.ndarray, arr_end: np.ndarray
211+
) -> np.ndarray:
202212
r"""
203-
Performs exponential interpolation between two NumPy arrays over their first dimension.
213+
Performs exponential convex combination between two NumPy arrays over their first dimension.
204214
205215
This function achieves an exponential-like transition by performing linear
206-
interpolation in the logarithmic space, suitable to interpolate over a dimension which has
207-
a growth factor.
216+
interpolation in the logarithmic space.
208217
209218
Parameters
210219
----------
@@ -225,6 +234,10 @@ def exponential_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.
225234
ValueError
226235
If `arr_start` and `arr_end` do not have the same shape.
227236
237+
See Also
238+
---------
239+
linear_interp_arrays: linear version of the interpolation.
240+
228241
Notes
229242
-----
230243
The interpolation is performed by transforming the arrays to a logarithmic
@@ -262,27 +275,49 @@ def exponential_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.
262275
return np.exp(interpolated_log_arr)
263276

264277

265-
class InterpolationStrategyBase(ABC):
278+
class ImpactInterpolationStrategy(ABC):
266279
r"""
267-
Base abstract class for defining a set of interpolation strategies.
280+
Base abstract class for defining a set of interpolation strategies for impact outputs.
268281
269282
This class serves as a blueprint for implementing specific interpolation
270-
methods (e.g., 'Linear', 'Exponential') across different impact dimensions:
271-
Exposure (matrices), Hazard, and Vulnerability (arrays/metrics).
283+
methods (e.g., 'Linear', 'Exponential') describing how impact outputs
284+
should evolve between two points in time.
285+
286+
Impacts result from three dimensions—Exposure, Hazard, and Vulnerability—
287+
each of which may change differently over time. Consequently, a distinct
288+
interpolation strategy is defined for each dimension.
289+
290+
Exposure interpolation differs from Hazard and Vulnerability interpolation.
291+
Changes in exposure do not alter the shape of the impact matrices, which
292+
allows direct interpolation of the matrices themselves. For the Exposure
293+
dimension, interpolation therefore consists of generating intermediate
294+
impact matrices between the two time points, with exposure evolving while
295+
hazard and vulnerability remain fixed (to either the first or second point).
296+
297+
In contrast, changes in Hazard may alter the
298+
set of events between the two time points, making direct interpolation of
299+
impact matrices impossible. Instead, impacts are first aggregated over the
300+
event dimension (i.e. the EAI metric). The evolution of impacts is then
301+
interpolated as a convex combination of metric sequences computed from two
302+
scenarios: one with hazard fixed at the initial time point and one with
303+
hazard fixed at the final time point.
304+
305+
The same aggregation-based interpolation approach is applied to the
306+
Vulnerability dimension.
272307
273308
Attributes
274309
----------
275310
exposure_interp : Callable
276-
The function used to interpolate sparse impact matrices over the
277-
exposure dimension.
311+
The function used to interpolate sparse impact matrices over time
312+
with changing exposure dimension.
278313
Signature: (mat_start, mat_end, num_points, **kwargs) -> list[sparse.csr_matrix].
279314
hazard_interp : Callable
280-
The function used to interpolate NumPy arrays of metrics over the
281-
hazard dimension.
315+
The function used to interpolate NumPy arrays of metrics over time
316+
with changing hazard dimension.
282317
Signature: (arr_start, arr_end, **kwargs) -> np.ndarray.
283318
vulnerability_interp : Callable
284-
The function used to interpolate NumPy arrays of metrics over the
285-
vulnerability dimension.
319+
The function used to interpolate NumPy arrays of metrics over time
320+
with changing vulnerability dimension.
286321
Signature: (arr_start, arr_end, **kwargs) -> np.ndarray.
287322
"""
288323

@@ -299,10 +334,11 @@ def interp_over_exposure_dim(
299334
**kwargs: Optional[Dict[str, Any]],
300335
) -> List[sparse.csr_matrix]:
301336
"""
302-
Interpolates between two impact matrices using the defined exposure strategy.
337+
Interpolates between two impact matrices using the defined strategy for the exposure
338+
dimension.
303339
304340
This method calls the function assigned to :attr:`exposure_interp` to generate
305-
a sequence of matrices.
341+
a sequence of impact matrices of length "interpolation_range".
306342
307343
Parameters
308344
----------
@@ -347,7 +383,8 @@ def interp_over_hazard_dim(
347383
**kwargs: Optional[Dict[str, Any]],
348384
) -> np.ndarray:
349385
"""
350-
Interpolates between two metric arrays using the defined hazard strategy.
386+
Generates the convex combination between two arrays of metrics using
387+
the defined interpolation strategy for the hazard dimension.
351388
352389
This method calls the function assigned to :attr:`hazard_interp`.
353390
@@ -375,7 +412,8 @@ def interp_over_vulnerability_dim(
375412
**kwargs: Optional[Dict[str, Any]],
376413
) -> np.ndarray:
377414
"""
378-
Interpolates between two metric arrays using the defined vulnerability strategy.
415+
Generates the convex combination between two arrays of metrics using
416+
the defined interpolation strategy for the hazard dimension.
379417
380418
This method calls the function assigned to :attr:`vulnerability_interp`.
381419
@@ -397,10 +435,10 @@ def interp_over_vulnerability_dim(
397435
return self.vulnerability_interp(metric_0, metric_1, **kwargs)
398436

399437

400-
class InterpolationStrategy(InterpolationStrategyBase):
438+
class CustomImpactInterpolationStrategy(ImpactInterpolationStrategy):
401439
r"""Interface for interpolation strategies.
402440
403-
This is the class to use to define your own custom interpolation strategy.
441+
This is the class to use to define custom interpolation strategies.
404442
"""
405443

406444
def __init__(
@@ -415,21 +453,21 @@ def __init__(
415453
self.vulnerability_interp = vulnerability_interp
416454

417455

418-
class AllLinearStrategy(InterpolationStrategyBase):
456+
class AllLinearStrategy(ImpactInterpolationStrategy):
419457
r"""Linear interpolation strategy over all dimensions."""
420458

421459
def __init__(self) -> None:
422460
super().__init__()
423-
self.exposure_interp = linear_interp_imp_mat
424-
self.hazard_interp = linear_interp_arrays
425-
self.vulnerability_interp = linear_interp_arrays
461+
self.exposure_interp = linear_interp_matrix_elemwise
462+
self.hazard_interp = linear_convex_combination
463+
self.vulnerability_interp = linear_convex_combination
426464

427465

428-
class ExponentialExposureStrategy(InterpolationStrategyBase):
466+
class ExponentialExposureStrategy(ImpactInterpolationStrategy):
429467
r"""Exponential interpolation strategy for exposure and linear for Hazard and Vulnerability."""
430468

431469
def __init__(self) -> None:
432470
super().__init__()
433-
self.exposure_interp = exponential_interp_imp_mat
434-
self.hazard_interp = linear_interp_arrays
435-
self.vulnerability_interp = linear_interp_arrays
471+
self.exposure_interp = exponential_interp_matrix_elemwise
472+
self.hazard_interp = linear_convex_combination
473+
self.vulnerability_interp = linear_convex_combination

0 commit comments

Comments
 (0)