descent_minimizer.py 4.31 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
19
20
21

import abc
import numpy as np

Martin Reinecke's avatar
Martin Reinecke committed
22
from .minimizer import Minimizer
23
24
25
from .line_searching import LineSearchStrongWolfe


Martin Reinecke's avatar
Martin Reinecke committed
26
class DescentMinimizer(Minimizer):
27
28
29
30
31
32
33
    """ 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.

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

    """
43

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

49
    def __call__(self, energy):
50
        """ Performs the minimization of the provided Energy functional.
51
52
53
54

        Parameters
        ----------
        energy : Energy object
55
56
           Energy object which provides value, gradient and curvature at a
           specific position in parameter space.
57
58
59

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

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

72
73
74
        """

        f_k_minus_1 = None
Martin Reinecke's avatar
Martin Reinecke committed
75
76
77
78
        controller = self._controller
        status = controller.start(energy)
        if status != controller.CONTINUE:
            return E, status
79
80

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

86
            # current position is encoded in energy object
87
            descent_direction = self.get_descent_direction(energy)
88
89
            # compute the step length, which minimizes energy.value along the
            # search direction
Theo Steininger's avatar
Theo Steininger committed
90
            try:
91
                new_energy = \
Theo Steininger's avatar
Theo Steininger committed
92
93
94
95
96
97
98
                    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
99
                return energy, controller.ERROR
Theo Steininger's avatar
Theo Steininger committed
100

101
            f_k_minus_1 = energy.value
102
103
            # check if new energy value is bigger than old energy value
            if (new_energy.value - energy.value) > 0:
104
105
                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
106
                return energy, controller.ERROR
107

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

113
114

    @abc.abstractmethod
115
    def get_descent_direction(self, energy):
116
        raise NotImplementedError