Skip to content

Commit

Permalink
Add BayesianOptimizerOutputEvaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
peanutfun committed Nov 24, 2023
1 parent 8ab8600 commit 85a5826
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 261 deletions.
12 changes: 10 additions & 2 deletions climada/test/test_util_calibrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@

from climada.entity import ImpactFuncSet, ImpactFunc

from climada.util.calibrate import Input, ScipyMinimizeOptimizer, BayesianOptimizer, OutputEvaluator
from climada.util.calibrate import (
Input,
ScipyMinimizeOptimizer,
BayesianOptimizer,
OutputEvaluator,
BayesianOptimizerOutputEvaluator,
)

from climada.util.calibrate.test.test_base import hazard, exposure

Expand Down Expand Up @@ -213,6 +219,8 @@ def test_plots(self):
output_eval.impf_set.plot()
output_eval.plot_at_event()
output_eval.plot_at_region()
output_eval.plot_event_region_heatmap()

output_eval = BayesianOptimizerOutputEvaluator(self.input, output)
ax = output_eval.plot_impf_variability()
self.assertIsInstance(ax, Axes)
output_eval.plot_event_region_heatmap()
3 changes: 1 addition & 2 deletions climada/util/calibrate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Impact function calibration module"""

from .base import Input, OutputEvaluator
from .bayesian_optimizer import BayesianOptimizer
from .bayesian_optimizer import BayesianOptimizer, BayesianOptimizerOutputEvaluator
from .scipy_optimizer import ScipyMinimizeOptimizer
from .func import rmse, rmsf, impact_at_reg
136 changes: 0 additions & 136 deletions climada/util/calibrate/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,142 +201,6 @@ def __post_init__(self):
).impact(assign_centroids=True, save_mat=True)
self._impact_label = f"Impact [{self.input.exposure.value_unit}]"

def plot_impf_variability(
self,
cost_func_diff: float = 0.1,
p_space_df: Optional[pd.DataFrame] = None,
plot_haz: bool = True,
plot_impf_kws: Optional[dict] = None,
plot_hist_kws: Optional[dict] = None,
):
"""Plot impact function variability with parameter combinations of
almost equal cost function values
Args:
cost_func_diff (float, optional): Max deviation from optimal cost
function value (as fraction). Defaults to 0.1 (i.e. 10%).
p_space_df (pd.DataFrame, optional): parameter space. Defaults to None.
plot_haz (bool, optional): Whether or not to plot hazard intensity
distibution. Defaults to False.
plot_impf_kws (dict, optional): Keyword arguments for impact
function plot. Defaults to None.
plot_hist_kws (dict, optional): Keyword arguments for hazard
intensity distribution plot. Defaults to None.
"""

# Initialize plot keyword arguments
if plot_impf_kws is None:
plot_impf_kws = {}
if plot_hist_kws is None:
plot_hist_kws = {}

# Retrieve hazard type and parameter space
haz_type = self.input.hazard.haz_type
if p_space_df is None:
# Assert that self.output has the p_space_to_dataframe() method,
# which is defined for the BayesianOptimizerOutput class
if not hasattr(self.output, "p_space_to_dataframe"):
raise TypeError(
"To derive the full impact function parameter space, "
"plot_impf_variability() requires BayesianOptimizerOutput "
"as OutputEvaluator.output attribute, which provides the "
"method p_space_to_dataframe()."
)
p_space_df = self.output.p_space_to_dataframe()

# Retrieve parameters of impact functions with cost function values
# within 'cost_func_diff' % of the best estimate
params_within_range = p_space_df["Parameters"]
plot_space_label = "Parameter space"
if cost_func_diff is not None:
max_cost_func_val = p_space_df["Calibration", "Cost Function"].min() * (
1 + cost_func_diff
)
params_within_range = params_within_range.loc[
p_space_df["Calibration", "Cost Function"] <= max_cost_func_val
]
plot_space_label = (
f"within {int(cost_func_diff*100)} percent " f"of best fit"
)

# Set plot defaults
color = plot_impf_kws.pop("color", "tab:blue")
lw = plot_impf_kws.pop("lw", 2)
zorder = plot_impf_kws.pop("zorder", 3)
label = plot_impf_kws.pop("label", "best fit")

# get number of impact functions and create a plot for each
n_impf = len(self.impf_set.get_func(haz_type=haz_type))
axes = []

for impf_idx in range(n_impf):
_, ax = plt.subplots()

# Plot best-fit impact function
best_impf = self.impf_set.get_func(haz_type=haz_type)[impf_idx]
ax.plot(
best_impf.intensity,
best_impf.mdd * best_impf.paa * 100,
color=color,
lw=lw,
zorder=zorder,
label=label,
**plot_impf_kws,
)

# Plot all impact functions within 'cost_func_diff' % of best estimate
for row in range(params_within_range.shape[0]):
label_temp = plot_space_label if row == 0 else None

sel_params = params_within_range.iloc[row, :].to_dict()
temp_impf_set = self.input.impact_func_creator(**sel_params)
temp_impf = temp_impf_set.get_func(haz_type=haz_type)[impf_idx]

ax.plot(
temp_impf.intensity,
temp_impf.mdd * temp_impf.paa * 100,
color="grey",
alpha=0.4,
label=label_temp,
)

# Plot hazard intensity value distributions
if plot_haz:
haz_vals = self.input.hazard.intensity[
:, self.input.exposure.gdf[f"centr_{haz_type}"]
]

# Plot defaults
color_hist = plot_hist_kws.pop("color", "tab:orange")
alpha_hist = plot_hist_kws.pop("alpha", 0.3)

ax2 = ax.twinx()
ax2.hist(
haz_vals.data,
bins=40,
color=color_hist,
alpha=alpha_hist,
label="Hazard intensity\noccurence",
)
ax2.set(ylabel="Hazard intensity occurence (#Exposure points)")
ax.axvline(
x=haz_vals.max(), label="Maximum hazard value", color="tab:orange"
)
ax2.legend(loc="lower right")

ax.set(
xlabel=f"Intensity ({self.input.hazard.units})",
ylabel="Mean Damage Ratio (MDR) in %",
xlim=(min(best_impf.intensity), max(best_impf.intensity)),
)
ax.legend()
axes.append(ax)

if n_impf > 1:
return axes

return ax

def plot_at_event(
self,
data_transf: Callable[[pd.DataFrame], pd.DataFrame] = lambda x: x,
Expand Down
Loading

0 comments on commit 85a5826

Please sign in to comment.