Skip to content

Commit 9711379

Browse files
committed
format,doc: cleans up code, adds docstrings
1 parent 8666197 commit 9711379

File tree

6 files changed

+247
-485
lines changed

6 files changed

+247
-485
lines changed

climada/trajectories/impact_calc_strat.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,33 +124,25 @@ def calculate_residual_or_risk_transfer_impact_matrix(
124124
scipy.sparse.csr_matrix
125125
The adjusted impact matrix, either residual or risk transfer.
126126
127-
Example
128-
-------
129-
>>> calc_residual_or_risk_transf_imp_mat(imp_mat, attachment=100, cover=500, calc_residual=True)
130-
Residual impact matrix with applied risk layer adjustments.
131127
"""
128+
132129
if risk_transf_attach and risk_transf_cover:
133-
# Make a copy of the impact matrix
134130
imp_mat = copy.deepcopy(imp_mat)
135131
# Calculate the total impact per event
136132
total_at_event = imp_mat.sum(axis=1).A1
137133
# Risk layer at event
138134
transfer_at_event = np.minimum(
139135
np.maximum(total_at_event - risk_transf_attach, 0), risk_transf_cover
140136
)
141-
# Resiudal impact
142137
residual_at_event = np.maximum(total_at_event - transfer_at_event, 0)
143138

144139
# Calculate either the residual or transfer impact matrix
145140
# Choose the denominator to rescale the impact values
146141
if calc_residual:
147-
# Rescale the impact values
148142
numerator = residual_at_event
149143
else:
150-
# Rescale the impact values
151144
numerator = transfer_at_event
152145

153-
# Rescale the impact values
154146
rescale_impact_values = np.divide(
155147
numerator,
156148
total_at_event,

climada/trajectories/interpolation.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
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 different sparce matrices interpolation approaches.
20+
21+
"""
22+
123
import logging
224
from abc import ABC, abstractmethod
325

426
import numpy as np
5-
from scipy.sparse import lil_matrix
27+
from scipy.sparse import csr_matrix, lil_matrix
628

729
LOGGER = logging.getLogger(__name__)
830

@@ -21,7 +43,7 @@ def interpolate(self, imp_E0, imp_E1, time_points: int):
2143
try:
2244
return self.interpolate_imp_mat(imp_E0, imp_E1, time_points)
2345
except ValueError as e:
24-
if str(e) == "inconsistent shape":
46+
if str(e) == "inconsistent shapes":
2547
raise ValueError(
2648
"Interpolation between impact matrices of different shapes"
2749
)
@@ -61,7 +83,7 @@ def interpolate_sm(mat_start, mat_end, time, time_points):
6183
# Perform the linear interpolation
6284
mat_interpolated = mat_start + ratio * (mat_end - mat_start)
6385

64-
return mat_interpolated
86+
return csr_matrix(mat_interpolated)
6587

6688
LOGGER.debug(f"imp0: {imp0.imp_mat.data[0]}, imp1: {imp1.imp_mat.data[0]}")
6789
return [
@@ -70,19 +92,11 @@ def interpolate_sm(mat_start, mat_end, time, time_points):
7092
]
7193

7294

73-
class ExponentialInterpolation:
95+
class ExponentialInterpolation(InterpolationStrategy):
7496
"""Exponential interpolation strategy."""
7597

7698
def interpolate(self, imp_E0, imp_E1, time_points: int):
77-
try:
78-
return self.interpolate_imp_mat(imp_E0, imp_E1, time_points)
79-
except ValueError as e:
80-
if str(e) == "inconsistent shape":
81-
raise ValueError(
82-
"Interpolation between impact matrices of different shapes"
83-
)
84-
else:
85-
raise e
99+
return self.interpolate_imp_mat(imp_E0, imp_E1, time_points)
86100

87101
@staticmethod
88102
def interpolate_imp_mat(imp0, imp1, time_points):
@@ -119,15 +133,9 @@ def interpolate_sm(mat_start, mat_end, time, time_points):
119133
# Convert back to the original domain using the exponential function
120134
mat_interpolated = np.exp(log_mat_interpolated)
121135

122-
return lil_matrix(mat_interpolated)
136+
return csr_matrix(mat_interpolated)
123137

124138
return [
125139
interpolate_sm(imp0.imp_mat, imp1.imp_mat, time, time_points)
126140
for time in range(time_points)
127141
]
128-
129-
130-
# Example usage
131-
# Assuming imp0 and imp1 are instances of ImpactCalc with imp_mat attributes as sparse matrices
132-
# interpolator = ExponentialInterpolation()
133-
# interpolated_matrices = interpolator.interpolate(imp0, imp1, 100)

climada/trajectories/risk_trajectory.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import logging
2424

2525
import matplotlib.pyplot as plt
26-
import numpy as np
2726
import pandas as pd
2827

2928
from climada.entity.disc_rates.base import DiscRates

climada/trajectories/riskperiod.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,29 +41,63 @@
4141

4242

4343
def lazy_property(method):
44+
# This function is used as a decorator for properties
45+
# that require "heavy" computation and are not always needed
46+
# if the property is none, it uses the corresponding computation method
47+
# and stores the result in the corresponding private attribute
4448
attr_name = f"_{method.__name__}"
4549

4650
@property
4751
def _lazy(self):
4852
if getattr(self, attr_name) is None:
49-
meas_n = self.measure.name if self.measure else "no_measure"
50-
LOGGER.debug(
51-
f"Computing {method.__name__} for {self._snapshot0.date}-{self._snapshot1.date} with {meas_n}."
52-
)
53+
# meas_n = self.measure.name if self.measure else "no_measure"
54+
# LOGGER.debug(
55+
# f"Computing {method.__name__} for {self._snapshot0.date}-{self._snapshot1.date} with {meas_n}."
56+
# )
5357
setattr(self, attr_name, method(self))
5458
return getattr(self, attr_name)
5559

5660
return _lazy
5761

5862

5963
class CalcRiskPeriod:
60-
"""Handles the computation of impacts for a risk period."""
64+
"""Handles the computation of impacts for a risk period.
65+
66+
This object handles the interpolations and computations of risk metrics in
67+
between two given snapshots, along a DateTime index build on
68+
`interval_freq` or `time_points`.
69+
70+
Attributes
71+
----------
72+
73+
date_idx: pd.DateTimeIndex
74+
The date index for the different interpolated points between the two snapshots
75+
interpolation_strategy: InterpolationStrategy, optional
76+
The approach used to interpolate impact matrices in between the two snapshots, linear by default.
77+
impact_computation_strategy: ImpactComputationStrategy, optional
78+
The method used to calculate the impact from the (Haz,Exp,Vul) of the two snapshots.
79+
Defaults to ImpactCalc
80+
risk_transf_attach: float, optional
81+
The attachement of risk transfer to apply. Defaults to None.
82+
risk_transf_cover: float, optional
83+
The cover of risk transfer to apply. Defaults to None.
84+
calc_residual: bool, optional
85+
A boolean stating whether the residual (True) or transfered risk (False) is retained when doing
86+
the risk transfer. Defaults to False.
87+
measure: Measure, optional
88+
The measure to apply to both snapshots. Defaults to None.
89+
90+
Notes
91+
-----
92+
93+
This class is intended for internal computation. Users should favor `RiskTrajectory` objects.
94+
"""
6195

6296
def __init__(
6397
self,
6498
snapshot0: Snapshot,
6599
snapshot1: Snapshot,
66-
interval_freq: str | None = "YS",
100+
interval_freq: str | None = "AS-JAN",
67101
time_points: int | None = None,
68102
interpolation_strategy: InterpolationStrategy | None = None,
69103
impact_computation_strategy: ImpactComputationStrategy | None = None,
@@ -99,6 +133,7 @@ def _reset_impact_data(self):
99133
self._imp_mats_E0, self._imp_mats_E1 = None, None
100134
self._per_date_eai_H0, self._per_date_eai_H1 = None, None
101135
self._per_date_aai_H0, self._per_date_aai_H1 = None, None
136+
self._eai_gdf = None
102137
self._per_date_return_periods_H0, self._per_date_return_periods_H1 = None, None
103138

104139
@staticmethod
@@ -146,12 +181,18 @@ def _set_date_idx(
146181
periods=points,
147182
freq=freq, # type: ignore
148183
name=name,
184+
normalize=True,
149185
)
150186
if periods is not None and len(ret) != periods:
151187
raise ValueError(
152188
"Number of periods and frequency given to date_range are inconsistant"
153189
)
154190

191+
if pd.infer_freq(ret) != freq:
192+
LOGGER.debug(
193+
f"Given interval frequency ( {pd.infer_freq(ret)} ) and infered interval frequency differ ( {freq} )."
194+
)
195+
155196
return ret
156197

157198
@property
@@ -171,7 +212,7 @@ def date_idx(self, value, /):
171212
if not isinstance(value, pd.DatetimeIndex):
172213
raise ValueError("Not a DatetimeIndex")
173214

174-
self._date_idx = value
215+
self._date_idx = value.normalize()
175216
self._time_points = len(self.date_idx)
176217
self._interval_freq = pd.infer_freq(self.date_idx)
177218
self._prop_H1 = np.linspace(0, 1, num=self.time_points)

climada/trajectories/snapshot.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ class Snapshot:
4646
Notes
4747
-----
4848
49-
The object creates copies of the exposure hazard and impact function set.
49+
The object creates deep copies of the exposure hazard and impact function set.
5050
51-
To create a snapshot with a measure use Snapshot.apply_measure(measure).
51+
To create a snapshot with a measure, create a snapshot `snap` without
52+
the measure and call `snap.apply_measure(measure)`, which returns a new Snapshot object.
5253
"""
5354

5455
def __init__(
@@ -97,6 +98,18 @@ def _convert_to_date(date_arg) -> datetime.date:
9798
raise TypeError("date_arg must be an int, str, or datetime.date")
9899

99100
def apply_measure(self, measure: Measure):
101+
"""Create a new snapshot from a measure
102+
103+
This methods creates a new `Snapshot` object by applying a measure on
104+
the current one.
105+
106+
Parameters
107+
----------
108+
measure : Measure
109+
The measure to be applied to the snapshot
110+
111+
"""
112+
100113
LOGGER.debug(f"Applying measure {measure.name} on snapshot {id(self)}")
101114
exp_new, impfset_new, haz_new = measure.apply(
102115
self.exposure, self.impfset, self.hazard

0 commit comments

Comments
 (0)