Skip to content
88 changes: 58 additions & 30 deletions climada/trajectories/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
__all__ = [
"AllLinearStrategy",
"ExponentialExposureStrategy",
"linear_interp_arrays",
"linear_convex_combination",
"linear_interp_matrix_elemwise",
"exponential_interp_arrays",
"exponential_convex_combination",
"exponential_interp_matrix_elemwise",
]

Expand Down Expand Up @@ -148,9 +148,9 @@ def exponential_interp_matrix_elemwise(
return res


def linear_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarray:
def linear_convex_combination(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarray:
r"""
Performs linear interpolation between two n x m NumPy arrays over their
Performs a linear convex combination between two n x m NumPy arrays over their
first dimension (n rows).

This function interpolates each metric (column) linearly across the time steps
Expand Down Expand Up @@ -180,7 +180,9 @@ def linear_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarr
--------
>>> arr_start = [ [ 1, 1], [1, 2], [10, 20] ]
>>> arr_end = [ [2, 2], [5, 6], [10, 30] ]
>>> linear_interp_arrays(arr_start, arr_end) = [ [1, 1], [3, 4], [10, 30] ]
>>> linear_interp_arrays(arr_start, arr_end)
>>> [[1, 1], [3, 4], [10, 30]]

Notes
-----
The interpolation is performed element-wise along the first dimension
Expand All @@ -204,9 +206,11 @@ def linear_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarr
return np.multiply(arr_start, prop0) + np.multiply(arr_end, prop1)


def exponential_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.ndarray:
def exponential_convex_combination(
arr_start: np.ndarray, arr_end: np.ndarray
) -> np.ndarray:
r"""
Performs exponential interpolation between two NumPy arrays over their first dimension.
Performs exponential convex combination between two NumPy arrays over their first dimension.

This function achieves an exponential-like transition by performing linear
interpolation in the logarithmic space.
Expand All @@ -229,7 +233,7 @@ def exponential_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.
------
ValueError
If `arr_start` and `arr_end` do not have the same shape.

See Also
---------
linear_interp_arrays: linear version of the interpolation.
Expand Down Expand Up @@ -271,28 +275,49 @@ def exponential_interp_arrays(arr_start: np.ndarray, arr_end: np.ndarray) -> np.
return np.exp(interpolated_log_arr)


class InterpolationStrategyBase(ABC):
class ImpactInterpolationStrategy(ABC):
r"""
Base abstract class for defining a set of interpolation strategies for impact outputs.

This class serves as a blueprint for implementing specific interpolation
methods (e.g., 'Linear', 'Exponential') for impact outputs (impact matrices
or metrics such as aai_agg, eai_exp or at_event) across the input dimentions
Exposure (impact matrices only), Hazard, and Vulnerability (metrics only).
methods (e.g., 'Linear', 'Exponential') describing how impact outputs
should evolve between two points in time.

Impacts result from three dimensions—Exposure, Hazard, and Vulnerability—
each of which may change differently over time. Consequently, a distinct
interpolation strategy is defined for each dimension.

Exposure interpolation differs from Hazard and Vulnerability interpolation.
Changes in exposure do not alter the shape of the impact matrices, which
allows direct interpolation of the matrices themselves. For the Exposure
dimension, interpolation therefore consists of generating intermediate
impact matrices between the two time points, with exposure evolving while
hazard and vulnerability remain fixed (to either the first or second point).

In contrast, changes in Hazard may alter the
set of events between the two time points, making direct interpolation of
impact matrices impossible. Instead, impacts are first aggregated over the
event dimension (i.e. the EAI metric). The evolution of impacts is then
interpolated as a convex combination of metric sequences computed from two
scenarios: one with hazard fixed at the initial time point and one with
hazard fixed at the final time point.

The same aggregation-based interpolation approach is applied to the
Vulnerability dimension.

Attributes
----------
exposure_interp : Callable
The function used to interpolate sparse impact matrices over the
exposure dimension.
The function used to interpolate sparse impact matrices over time
with changing exposure dimension.
Signature: (mat_start, mat_end, num_points, **kwargs) -> list[sparse.csr_matrix].
hazard_interp : Callable
The function used to interpolate NumPy arrays of metrics over the
hazard dimension.
The function used to interpolate NumPy arrays of metrics over time
with changing hazard dimension.
Signature: (arr_start, arr_end, **kwargs) -> np.ndarray.
vulnerability_interp : Callable
The function used to interpolate NumPy arrays of metrics over the
vulnerability dimension.
The function used to interpolate NumPy arrays of metrics over time
with changing vulnerability dimension.
Signature: (arr_start, arr_end, **kwargs) -> np.ndarray.
"""

Expand All @@ -309,10 +334,11 @@ def interp_over_exposure_dim(
**kwargs: Optional[Dict[str, Any]],
) -> List[sparse.csr_matrix]:
"""
Interpolates between two impact matrices using the defined exposure strategy.
Interpolates between two impact matrices using the defined strategy for the exposure
dimension.

This method calls the function assigned to :attr:`exposure_interp` to generate
a sequence of matrices.
a sequence of impact matrices of length "interpolation_range".

Parameters
----------
Expand Down Expand Up @@ -357,7 +383,8 @@ def interp_over_hazard_dim(
**kwargs: Optional[Dict[str, Any]],
) -> np.ndarray:
"""
Interpolates between two metric arrays using the defined hazard strategy.
Generates the convex combination between two arrays of metrics using
the defined interpolation strategy for the hazard dimension.

This method calls the function assigned to :attr:`hazard_interp`.

Expand Down Expand Up @@ -385,7 +412,8 @@ def interp_over_vulnerability_dim(
**kwargs: Optional[Dict[str, Any]],
) -> np.ndarray:
"""
Interpolates between two metric arrays using the defined vulnerability strategy.
Generates the convex combination between two arrays of metrics using
the defined interpolation strategy for the hazard dimension.

This method calls the function assigned to :attr:`vulnerability_interp`.

Expand All @@ -407,10 +435,10 @@ def interp_over_vulnerability_dim(
return self.vulnerability_interp(metric_0, metric_1, **kwargs)


class InterpolationStrategy(InterpolationStrategyBase):
class CustomImpactInterpolationStrategy(ImpactInterpolationStrategy):
r"""Interface for interpolation strategies.

This is the class to use to define your own custom interpolation strategy.
This is the class to use to define custom interpolation strategies.
"""

def __init__(
Expand All @@ -425,21 +453,21 @@ def __init__(
self.vulnerability_interp = vulnerability_interp


class AllLinearStrategy(InterpolationStrategyBase):
class AllLinearStrategy(ImpactInterpolationStrategy):
r"""Linear interpolation strategy over all dimensions."""

def __init__(self) -> None:
super().__init__()
self.exposure_interp = linear_interp_matrix_elemwise
self.hazard_interp = linear_interp_arrays
self.vulnerability_interp = linear_interp_arrays
self.hazard_interp = linear_convex_combination
self.vulnerability_interp = linear_convex_combination


class ExponentialExposureStrategy(InterpolationStrategyBase):
class ExponentialExposureStrategy(ImpactInterpolationStrategy):
r"""Exponential interpolation strategy for exposure and linear for Hazard and Vulnerability."""

def __init__(self) -> None:
super().__init__()
self.exposure_interp = exponential_interp_matrix_elemwise
self.hazard_interp = linear_interp_arrays
self.vulnerability_interp = linear_interp_arrays
self.hazard_interp = linear_convex_combination
self.vulnerability_interp = linear_convex_combination
Loading