descent_minimizer.py 4.38 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
# This program 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, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
Theo Steininger's avatar
Theo Steininger committed
13
14
15
16
17
#
# Copyright(C) 2013-2017 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
18

Martin Reinecke's avatar
Martin Reinecke committed
19
from __future__ import division
20
21
22
23
import abc

import numpy as np

Martin Reinecke's avatar
Martin Reinecke committed
24
from .minimizer import Minimizer
25
from .line_searching import LineSearchStrongWolfe
Martin Reinecke's avatar
Martin Reinecke committed
26
from future.utils import with_metaclass
27
28


Martin Reinecke's avatar
Martin Reinecke committed
29
class DescentMinimizer(Minimizer):
30
31
32
33
34
35
36
    """ A base class used by gradient methods to find a local minimum.

    Descent minimization methods are used to find a local minimum of a scalar
    function by following a descent direction. This class implements the
    minimization procedure once a descent direction is known. The descent
    direction has to be implemented separately.

37
38
    Parameters
    ----------
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
39
40
    controller : IterationController
        Object that decides when to terminate the minimization.
41
42
43
44
45
    line_searcher : callable *optional*
        Function which infers the step size in the descent direction
        (default : LineSearchStrongWolfe()).

    """
46

Martin Reinecke's avatar
Martin Reinecke committed
47
48
49
    def __init__(self, controller, line_searcher=LineSearchStrongWolfe()):
        super(DescentMinimizer, self).__init__()
        self._controller = controller
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
50
        self.line_searcher = line_searcher
51

52
    def __call__(self, energy):
53
        """ Performs the minimization of the provided Energy functional.
54
55
56
57

        Parameters
        ----------
        energy : Energy object
58
59
           Energy object which provides value, gradient and curvature at a
           specific position in parameter space.
60
61
62

        Returns
        -------
63
        energy : Energy object
64
            Latest `energy` of the minimization.
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
65
66
        status : integer
            Can be controller.CONVERGED or controller.ERROR
67

68
69
        Note
        ----
70
        The minimization is stopped if
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
71
            * the controller returns controller.CONVERGED or controller.ERROR,
72
73
74
            * a perfectly flat point is reached,
            * according to the line-search the minimum is found,

75
76
77
        """

        f_k_minus_1 = None
Martin Reinecke's avatar
Martin Reinecke committed
78
79
80
        controller = self._controller
        status = controller.start(energy)
        if status != controller.CONTINUE:
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
81
            return energy, status
82
83

        while True:
84
            # check if position is at a flat point
Martin Reinecke's avatar
Martin Reinecke committed
85
            if energy.gradient_norm == 0:
86
                self.logger.info("Reached perfectly flat point. Stopping.")
Martin Reinecke's avatar
Martin Reinecke committed
87
                return energy, controller.CONVERGED
88

89
            # current position is encoded in energy object
90
            descent_direction = self.get_descent_direction(energy)
91
92
            # compute the step length, which minimizes energy.value along the
            # search direction
Theo Steininger's avatar
Theo Steininger committed
93
            try:
94
                new_energy = \
Theo Steininger's avatar
Theo Steininger committed
95
96
97
98
99
100
101
                    self.line_searcher.perform_line_search(
                                                   energy=energy,
                                                   pk=descent_direction,
                                                   f_k_minus_1=f_k_minus_1)
            except RuntimeError:
                self.logger.warn(
                        "Stopping because of RuntimeError in line-search")
Martin Reinecke's avatar
Martin Reinecke committed
102
                return energy, controller.ERROR
Theo Steininger's avatar
Theo Steininger committed
103

104
            f_k_minus_1 = energy.value
105
106
            # check if new energy value is bigger than old energy value
            if (new_energy.value - energy.value) > 0:
107
108
                self.logger.info("Line search algorithm returned a new energy "
                                 "that was larger than the old one. Stopping.")
Martin Reinecke's avatar
Martin Reinecke committed
109
                return energy, controller.ERROR
110

111
            energy = new_energy
Martin Reinecke's avatar
Martin Reinecke committed
112
113
114
115
            status = self._controller.check(energy)
            if status != controller.CONTINUE:
                return energy, status

116
117

    @abc.abstractmethod
118
    def get_descent_direction(self, energy):
119
        raise NotImplementedError