|
6 | 6 | from numbers import Number |
7 | 7 |
|
8 | 8 | import pandas as pd |
| 9 | +import numpy as np |
9 | 10 | from scipy.optimize import Bounds, LinearConstraint, NonlinearConstraint |
| 11 | +import seaborn as sns |
10 | 12 |
|
11 | 13 | from climada.hazard import Hazard |
12 | 14 | from climada.entity import Exposures, ImpactFuncSet |
13 | 15 | from climada.engine import Impact, ImpactCalc |
| 16 | +import climada.util.coordinates as u_coord |
14 | 17 |
|
15 | 18 | ConstraintType = Union[LinearConstraint, NonlinearConstraint, Mapping] |
16 | 19 |
|
@@ -94,6 +97,113 @@ class Output: |
94 | 97 | target: Number |
95 | 98 |
|
96 | 99 |
|
| 100 | +@dataclass |
| 101 | +class OutputEvaluator: |
| 102 | + """Evaluate the output of a calibration task |
| 103 | +
|
| 104 | + Parameters |
| 105 | + ---------- |
| 106 | + input : Input |
| 107 | + The input object for the optimization task. |
| 108 | + output : Output |
| 109 | + The output object returned by the optimization task. |
| 110 | +
|
| 111 | + Attributes |
| 112 | + ---------- |
| 113 | + impf_set : climada.entity.ImpactFuncSet |
| 114 | + The impact function set built from the optimized parameters |
| 115 | + impact : climada.engine.Impact |
| 116 | + An impact object calculated using the optimal :py:attr:`impf_set` |
| 117 | + """ |
| 118 | + |
| 119 | + input: Input |
| 120 | + output: Output |
| 121 | + |
| 122 | + def __post_init__(self): |
| 123 | + """Compute the impact for the optimal parameters""" |
| 124 | + self.impf_set = self.input.impact_func_creator(**self.output.params) |
| 125 | + self.impact = ImpactCalc( |
| 126 | + exposures=self.input.exposure, |
| 127 | + impfset=self.impf_set, |
| 128 | + hazard=self.input.hazard, |
| 129 | + ).impact(assign_centroids=True, save_mat=True) |
| 130 | + self._impact_label = f"Impact [{self.input.exposure.value_unit}]" |
| 131 | + |
| 132 | + def plot_impf_set(self, **plot_kwargs): |
| 133 | + """Plot the optimized impact functions""" |
| 134 | + return self.impf_set.plot(**plot_kwargs) |
| 135 | + |
| 136 | + def plot_at_event(self, **plot_kwargs): |
| 137 | + data = ( |
| 138 | + pd.concat( |
| 139 | + [ |
| 140 | + pd.Series([self.impact.at_event]), |
| 141 | + self.input.data.sum(axis="columns"), |
| 142 | + ], |
| 143 | + ignore_index=True, |
| 144 | + axis=1, |
| 145 | + ) |
| 146 | + .rename(columns={0: "Model", 1: "Data"}) |
| 147 | + .set_index(self.input.hazard.event_name) |
| 148 | + ) |
| 149 | + ylabel = plot_kwargs.pop("ylabel", self._impact_label) |
| 150 | + return data.plot.bar(ylabel=ylabel, **plot_kwargs) |
| 151 | + |
| 152 | + def plot_at_region(self, agg_regions=None, **plot_kwargs): |
| 153 | + data = pd.concat( |
| 154 | + [ |
| 155 | + self.impact.impact_at_reg(agg_regions).sum(axis="index"), |
| 156 | + self.input.data.sum(axis="index"), |
| 157 | + ], |
| 158 | + axis=1, |
| 159 | + ).rename(columns={0: "Model", 1: "Data"}) |
| 160 | + |
| 161 | + # Use nice country names if no agg_regions were given |
| 162 | + if agg_regions is None: |
| 163 | + data = data.rename( |
| 164 | + index=lambda x: u_coord.country_to_iso(x, representation="name") |
| 165 | + ) |
| 166 | + |
| 167 | + ylabel = plot_kwargs.pop("ylabel", self._impact_label) |
| 168 | + return data.plot.bar(ylabel=ylabel, **plot_kwargs) |
| 169 | + |
| 170 | + def plot_event_region_heatmap(self, agg_regions=None, **plot_kwargs): |
| 171 | + # Data preparation |
| 172 | + agg = self.impact.impact_at_reg(agg_regions) |
| 173 | + data = (agg + 1) / (self.input.data + 1) |
| 174 | + data = data.transform(np.log10).replace(0, np.nan) |
| 175 | + data = data.where((agg < 1) & (self.input.data < 1)) |
| 176 | + |
| 177 | + # Use nice country names if no agg_regions were given |
| 178 | + if agg_regions is None: |
| 179 | + data = data.rename( |
| 180 | + index=lambda x: u_coord.country_to_iso(x, representation="name") |
| 181 | + ) |
| 182 | + |
| 183 | + # Default plot settings |
| 184 | + annot = plot_kwargs.pop("annot", True) |
| 185 | + vmax = plot_kwargs.pop("vmax", 3) |
| 186 | + vmin = plot_kwargs.pop("vmin", -vmax) |
| 187 | + center = plot_kwargs.pop("center", 0) |
| 188 | + fmt = plot_kwargs.pop("fmt", ".1f") |
| 189 | + cmap = plot_kwargs.pop("cmap", "RdBu_r") |
| 190 | + cbar_kws = plot_kwargs.pop( |
| 191 | + "cbar_kws", {"label": r"Model Error $\log_{10}(\mathrm{Impact})$"} |
| 192 | + ) |
| 193 | + |
| 194 | + return sns.heatmap( |
| 195 | + data, |
| 196 | + annot=annot, |
| 197 | + vmin=vmin, |
| 198 | + vmax=vmax, |
| 199 | + center=center, |
| 200 | + fmt=fmt, |
| 201 | + cmap=cmap, |
| 202 | + cbar_kws=cbar_kws, |
| 203 | + **plot_kwargs, |
| 204 | + ) |
| 205 | + |
| 206 | + |
97 | 207 | @dataclass |
98 | 208 | class Optimizer(ABC): |
99 | 209 | """Abstract base class (interface) for an optimization |
|
0 commit comments