Source code for upoqa.utils.result

# This file contains modified code from SciPy (https://github.com/scipy/scipy)
# which is licensed under the BSD 3-Clause License:
#
# Copyright (c) 2001-2002 Enthought, Inc. 2003, SciPy Developers.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The modifications to this file and the overall project are licensed under the
# GNU General Public License v3.0 (GPLv3). See the project's LICENSE file for
# the full GPLv3 terms.

"""
Optimization Result
===================

Adapted from ``scipy.optimize.OptimizeResult`` with minor modifications.
"""

import numpy as np
from typing import Iterable


[docs] class OptimizeResult(dict): """ Represent the optimization result. Attributes ---------- message : str Description of the cause of the termination. success : bool Whether the optimization procedure terminated successfully. exception : Exception, optional Exception raised during the optimization that caused the termination, if any. traceback : str, optional Traceback of the exception, if any. fun : float Value of objective function evaluated at the solution. funs : dict or list Values of the element functions evaluated at the solution. The type matches the input ``fun``: - If ``fun`` was a dictionary, return a dictionary mapping element names to their function values. - If ``fun`` was a list, return a list of function values in the same order as the input list. extra_fun : float Value of the extra function at the solution. The "extra function" represents the differentiable white-box component of the objective. x : ndarray The solution of the optimization. jac, hess : ndarray Values of objective function's Jacobian and its Hessian at ``x``. The Hessian is an approximation. nit : int Number of iterations performed by the optimizer nfev : dict or list Number of evaluations of element functions. The type matches the input ``fun``: - If ``fun`` was a dictionary, return a dictionary mapping element names to their numbers of function evaluations. - If ``fun`` was a list, return a list of evaluation numbers in the same order as the input list. max_nfev : int Maximum number of evaluations of element functions. avg_nfev : float Average number of evaluations of element functions. nrun : int Number of runs (when enabling restarts). manager : :class:`~upoqa.utils.manager.UPOQAManager`, optional Algorithm manager (debugging only). Included only when ``debug=True`` or ``return_internals=True``. interp_set : :class:`~upoqa.utils.interp_set.OverallInterpSet`, optional Interpolation point set (debugging only). Included only when ``debug=True`` or ``return_internals=True``. model : :class:`~upoqa.utils.model.OverallSurrogate`, optional Surrogate model (debugging only). Included only when ``debug=True`` or ``return_internals=True``. """ def __getattr__(self, name): try: return self[name] except KeyError as e: raise AttributeError(name) from e __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ def __repr__(self): order_keys = [ "message", "success", "exception", "traceback", "fun", "funs", "extra_fun", "x", "jac", "hess", "nit", "nfev", "max_nfev", "avg_nfev", "nrun", "manager", "interp_set", "model", ] order_keys = getattr(self, "_order_keys", order_keys) # 'slack', 'con' are redundant with residuals # 'crossover_nit' is probably not interesting to most users omit_keys = {"slack", "con", "crossover_nit", "_order_keys"} def key(item): try: return order_keys.index(item[0].lower()) except ValueError: # item not in list return np.inf def omit_redundant(items): for item in items: if item[0] in omit_keys: continue yield item def item_sorter(d): return sorted(omit_redundant(d.items()), key=key) if self.keys(): return _dict_formatter(self, sorter=item_sorter) else: return self.__class__.__name__ + "()" def __dir__(self): return list(self.keys())
def _dict_formatter(d, n=0, mplus=1, sorter=None): """ Pretty printer for dictionaries ``n`` keeps track of the starting indentation; lines are indented by this much after a line break. ``mplus`` is additional left padding applied to keys """ if isinstance(d, dict) and isinstance(next(iter(d)), Iterable): m = max(map(len, list(d.keys()))) + mplus # width to print keys s = "\n".join( [ k.rjust(m) + ": " # right justified, width m + _indenter(_dict_formatter(v, m + n + 2, 0, sorter), m + 2) for k, v in sorter(d) ] ) # +2 for ': ' else: # By default, NumPy arrays print with linewidth=76. `n` is # the indent at which a line begins printing, so it is subtracted # from the default to avoid exceeding 76 characters total. # `edgeitems` is the number of elements to include before and after # ellipses when arrays are not shown in full. # `threshold` is the maximum number of elements for which an # array is shown in full. # These values tend to work well for use with OptimizeResult. with np.printoptions( linewidth=76 - n, edgeitems=2, threshold=12, formatter={"float_kind": _float_formatter_10}, ): s = str(d) return s def _indenter(s, n=0): """ Ensures that lines after the first are indented by the specified amount """ split = s.split("\n") indent = " " * n return ("\n" + indent).join(split) def _float_formatter_10(x): """ Returns a string representation of a float with exactly ten characters """ if np.isposinf(x): return " inf" elif np.isneginf(x): return " -inf" elif np.isnan(x): return " nan" return np.format_float_scientific(x, precision=3, pad_left=2, unique=False)