Source code for climada.util.calibrate.scipy_optimizer

"""
This file is part of CLIMADA.

Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.

CLIMADA is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free
Software Foundation, version 3.

CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.

---
Calibration with scipy.minimize
"""

from dataclasses import dataclass
from typing import Any, Dict, List, Mapping

import numpy as np
from scipy.optimize import OptimizeResult, minimize

from .base import Optimizer, Output


[docs] @dataclass class ScipyMinimizeOptimizerOutput(Output): """Output of a calibration with :py:class:`ScipyMinimizeOptimizer` Attributes ---------- result : scipy.minimize.OptimizeResult The OptimizeResult instance returned by ``scipy.optimize.minimize``. """ result: OptimizeResult
[docs] @dataclass class ScipyMinimizeOptimizer(Optimizer): """An optimization using scipy.optimize.minimize By default, this optimizer uses the ``"trust-constr"`` method. This is advertised as the most general minimization method of the ``scipy`` package and supports bounds and constraints on the parameters. Users are free to choose any method of the catalogue, but must be aware that they might require different input parameters. These can be supplied via additional keyword arguments to :py:meth:`run`. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html for details. Parameters ---------- input : Input The input data for this optimizer. Supported data types for :py:attr:`constraint` might vary depending on the minimization method used. """ def __post_init__(self): """Create a private attribute for storing the parameter names""" self._param_names: List[str] = list() def _kwargs_to_impact_func_creator(self, *args, **_) -> Dict[str, Any]: """Transform the array of parameters into key-value pairs""" return dict(zip(self._param_names, args[0].flat)) def _select_by_param_names(self, mapping: Mapping[str, Any]) -> List[Any]: """Return a list of entries from a map with matching keys or ``None``""" return [mapping.get(key) for key in self._param_names]
[docs] def run(self, **opt_kwargs) -> ScipyMinimizeOptimizerOutput: """Execute the optimization Parameters ---------- params_init : Mapping (str, Number) The initial guess for all parameters as key-value pairs. method : str, optional The minimization method applied. Defaults to ``"trust-constr"``. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html for details. kwargs Additional keyword arguments passed to ``scipy.optimize.minimize``. Returns ------- output : ScipyMinimizeOptimizerOutput The output of the optimization. The :py:attr:`ScipyMinimizeOptimizerOutput.result` attribute stores the associated ``scipy.optimize.OptimizeResult`` instance. """ # Parse kwargs try: params_init = opt_kwargs.pop("params_init") except KeyError as err: raise RuntimeError( "ScipyMinimizeOptimizer.run requires 'params_init' mapping as argument" ) from err method = opt_kwargs.pop("method", "trust-constr") # Store names to rebuild dict when the minimize iterator returns an array self._param_names = list(params_init.keys()) # Transform bounds to match minimize input bounds = ( self._select_by_param_names(self.input.bounds) if self.input.bounds is not None else None ) x0 = np.array(list(params_init.values())) # pylint: disable=invalid-name res = minimize( fun=self._opt_func, x0=x0, bounds=bounds, constraints=self.input.constraints, method=method, **opt_kwargs, ) params = dict(zip(self._param_names, res.x.flat)) return ScipyMinimizeOptimizerOutput(params=params, target=res.fun, result=res)