Source code for minkit.minimization.minuit_api

########################################
# MIT License
#
# Copyright (c) 2020 Miguel Ramos Pernas
########################################
'''
Definition of the interface functions and classes with :mod:`iminuit`.
'''
from ..base import data_types
from ..base import parameters
from . import core

import contextlib
import functools
import iminuit

__all__ = ['MinuitMinimizer']

# Definition of the errors. This is given from the nature of the FCNs. If this is
# changed the output of the FCNs must change accordingly. A value of 1 means
# that the output of the FCNs is a chi-square-like function.
ERRORDEF = 1.

MINUIT = 'minuit'


def use_const_cache(method):
    '''
    Use the constant cache of the evaluator when calling the method.
    '''
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        with self.evaluator.using_caches():
            return method(self, *args, **kwargs)
    return wrapper


def registry_to_minuit_input(registry):
    '''
    Transform a registry of parameters into a dictionary to be parsed by Minuit.

    :param registry: registry of parameters.
    :type registry: Registry(Parameter)
    :returns: Minuit configuration dictionary.
    :rtype: dict
    '''
    values = {v.name: v.value for v in registry}
    # 0 for Minuit, None for MinKit
    errors = {f'error_{v.name}': v.error for v in registry if v.error is not None}
    limits = {f'limit_{v.name}': v.bounds for v in registry}
    const = {f'fix_{v.name}': v.constant for v in registry}
    return dict(errordef=ERRORDEF, **values, **errors, **limits, **const)


[docs]class MinuitMinimizer(core.Minimizer): def __init__(self, evaluator, **minimizer_config): ''' Interface with the :class:`iminuit.Minuit` class. In the calls to the different methods of this class it is ensured that the parameters of the PDF(s) from the evaluator instance have their value set to that of the minimum after a minimization process. :param evaluator: evaluator to be used in the minimization. :type evaluator: UnbinnedEvaluator, BinnedEvaluator or SimultaneousEvaluator ''' super().__init__(evaluator) self.__args = evaluator.args self.__minuit = iminuit.Minuit(evaluator, forced_parameters=self.__args.names, pedantic=False, **minimizer_config, **registry_to_minuit_input(self.__args)) def _asym_error(self, par, bound, var=1, atol=core.DEFAULT_ASYM_ERROR_ATOL, rtol=core.DEFAULT_ASYM_ERROR_RTOL, max_call=None): with self._restore_minuit(): return super()._asym_error(par, bound, var, atol, rtol, max_call) @contextlib.contextmanager def _restore_minuit(self): ''' Create a new context where the class holds a new Minuit instance, with the same parameters as the previous but with no information about the minimization state. ''' previous = self.__minuit self.__minuit = iminuit.Minuit(self.evaluator, forced_parameters=self.__args.names, pedantic=False, print_level=previous.print_level, **self.__minuit.fitarg) yield self self.__minuit = previous def _set_parameter_state(self, par, value=None, error=None, fixed=None): super()._set_parameter_state(par, value, error, fixed) if value is not None: self.__minuit.values[par.name] = value if error is not None: self.__minuit.errors[par.name] = error if fixed is not None: self.__minuit.fixed[par.name] = fixed def _update_args(self, params): ''' Update the parameters using the information from the Minuit result. :param params: list of parameters. :type params: iminuit.util.Params ''' for p in params: a = self.__args.get(p.name) a.value, a.error = p.value, p.error @property def minuit(self): ''' Underlying :class:`iminuit.Minuit` object. ''' return self.__minuit
[docs] @use_const_cache def hesse(self, *args, **kwargs): ''' Arguments are forwarded to the :py:meth:`iminuit.Minuit.hesse` function, and the values of the parameters are set to those from the minimization result. :returns: Output from :py:meth:`iminuit.Minuit.hesse`. ''' with self.__args.restoring_state(): params = self.__minuit.hesse(*args, *kwargs) self._update_args(params) return params
[docs] @use_const_cache def migrad(self, *args, **kwargs): ''' Arguments are forwarded to the :py:meth:`iminuit.Minuit.migrad` function, and the values of the parameters are set to those from the minimization result. :returns: output from :py:meth:`iminuit.Minuit.migrad`. .. seealso:: :meth:`MinuitMinimizer.minimize` ''' result, params = self.__minuit.migrad(*args, *kwargs) self._update_args(params) return result, params
[docs] def minimization_profile(self, wa, values, minimization_results=False, minimizer_config=None): with self._restore_minuit(): return super().minimization_profile(wa, values, minimization_results, minimizer_config)
[docs] @use_const_cache def minimize(self, *args, **kwargs): ''' Same as :meth:`MinuitMinimizer.migrad`, but offering a common interface for all the :class:`Minimizer` objects. It returns a tuple with the information whether the minimization succeded and the covariance matrix. .. seealso:: :meth:`MinuitMinimizer.migrad` ''' res, _ = self.migrad(*args, **kwargs) if res.is_valid: cov = data_types.empty_float((len(self.__args), len(self.__args))) non_fixed = parameters.Registry( filter(lambda p: not p.constant, self.__args)) for i, p1 in enumerate(non_fixed): for j, p2 in enumerate(non_fixed[i:]): cov[i][j] = cov[j][i] = self.__minuit.covariance[( p1.name, p2.name)] else: cov = None return core.MinimizationResult(res.is_valid, res.fval, cov)
[docs] @use_const_cache def minos(self, *args, **kwargs): ''' Arguments are forwarded to the :py:meth:`iminuit.Minuit.minos` function, and the values of the parameters are set to those of the MINOS result. If a new minimum is found, the value of the parameter is set accordingly. :returns: output from :py:meth:`iminuit.Minuit.minos`. .. seealso:: :meth:`MinuitMinimizer.asymmetric_errors` ''' with self.__args.restoring_state(): # to preserve the information of the minimum result = self.__minuit.minos(*args, *kwargs) for k, v in result.items(): a = self.__args.get(k) a.value = v.min a.asym_errors = abs(v.lower), v.upper # lower error is negative return result
[docs] def minos_profile(self, *args, **kwargs): ''' Arguments are forwarded to the :py:meth:`iminuit.Minuit.mnprofile` function. ''' return self.__minuit.mnprofile(*args, **kwargs)
[docs] @contextlib.contextmanager def restoring_state(self): ''' Method to ensure that modifications of parameters within a minimizer context are reset properly. Sadly, the :class:`iminuit.Minuit` class is not stateless, so each time a parameter is modified it must be notified of the change. .. warning:: This does not preserve the minimization state of MIGRAD. ''' with super().restoring_state(): # restores the parameters values, errors, fixed = zip( *[(p.value, p.error, p.constant) for p in self.evaluator.args]) yield self for p, v, e, f in zip(self.evaluator.args, values, errors, fixed): self.__minuit.values[p.name] = v # 0 for Minuit, None for MinKit self.__minuit.errors[p.name] = e or 0. self.__minuit.fixed[p.name] = f