Source code for estimagic.optimization.optimize_result

from dataclasses import dataclass, field
from typing import Any, Dict, Union

import numpy as np
import pandas as pd

from estimagic.utilities import to_pickle
from estimagic.compat import pd_df_map


[docs]@dataclass class OptimizeResult: """Optimization result object. **Attributes** Attributes: params (Any): The optimal parameters. criterion (float): The optimal criterion value. start_criterion (float): The criterion value at the start parameters. start_params (Any): The start parameters. algorithm (str): The algorithm used for the optimization. direction (str): Maximize or minimize. n_free (int): Number of free parameters. message (Union[str, None] = None): Message returned by the underlying algorithm. success (Union[bool, None] = None): Whether the optimization was successful. n_criterion_evaluations (Union[int, None] = None): Number of criterion evaluations. n_derivative_evaluations (Union[int, None] = None): Number of derivative evaluations. n_iterations (Union[int, None] = None): Number of iterations until termination. history (Union[Dict, None] = None): Optimization history. convergence_report (Union[Dict, None] = None): The convergence report. multistart_info (Union[Dict, None] = None): Multistart information. algorithm_output (Dict = field(default_factory=dict)): Additional algorithm specific information. """ params: Any criterion: float start_criterion: float start_params: Any algorithm: str direction: str n_free: int message: Union[str, None] = None success: Union[bool, None] = None n_criterion_evaluations: Union[int, None] = None n_derivative_evaluations: Union[int, None] = None n_iterations: Union[int, None] = None history: Union[Dict, None] = None convergence_report: Union[Dict, None] = None multistart_info: Union[Dict, None] = None algorithm_output: Dict = field(default_factory=dict) def __repr__(self): first_line = ( f"{self.direction.title()} with {self.n_free} free parameters terminated" ) if self.success is not None: snippet = "successfully" if self.success else "unsuccessfully" first_line += f" {snippet}" counters = [ ("criterion evaluations", self.n_criterion_evaluations), ("derivative evaluations", self.n_derivative_evaluations), ("iterations", self.n_iterations), ] counters = [(n, v) for n, v in counters if v is not None] if counters: name, val = counters[0] counter_msg = f"after {val} {name}" if len(counters) >= 2: for name, val in counters[1:-1]: counter_msg += f", {val} {name}" name, val = counters[-1] counter_msg += f" and {val} {name}" first_line += f" {counter_msg}" first_line += "." if self.message: message = f"The {self.algorithm} algorithm reported: {self.message}" else: message = None if self.start_criterion is not None and self.criterion is not None: improvement = ( f"The value of criterion improved from {self.start_criterion} to " f"{self.criterion}." ) else: improvement = None if self.convergence_report is not None: convergence = _format_convergence_report( self.convergence_report, self.algorithm ) else: convergence = None sections = [first_line, improvement, message, convergence] sections = [sec for sec in sections if sec is not None] msg = "\n\n".join(sections) return msg
[docs] def to_pickle(self, path): """Save the OptimizeResult object to pickle. Args: path (str, pathlib.Path): A str or pathlib.path ending in .pkl or .pickle. """ to_pickle(self, path=path)
def _format_convergence_report(report, algorithm): report = pd.DataFrame.from_dict(report) columns = ["one_step", "five_steps"] table = pd_df_map(report[columns], _format_float).astype(str) for col in "one_step", "five_steps": table[col] = table[col] + _create_stars(report[col]) table = table.to_string(justify="center") introduction = ( f"Independent of the convergence criteria used by {algorithm}, " "the strength of convergence can be assessed by the following criteria:" ) explanation = ( "(***: change <= 1e-10, **: change <= 1e-8, *: change <= 1e-5. " "Change refers to a change between accepted steps. The first column only " "considers the last step. The second column considers the last five steps.)" ) out = "\n\n".join([introduction, table, explanation]) return out def _create_stars(sr): stars = pd.cut( sr, bins=[-np.inf, 1e-10, 1e-8, 1e-5, np.inf], labels=["***", "** ", "* ", " "], ).astype("str") return stars def _format_float(number): """Round to four significant digits.""" return f"{number:.4g}"