descent_minimizer.py 5.24 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
    ----------
36
37
38
39
40
41
42
43
    line_searcher : callable *optional*
        Function which infers the step size in the descent direction
        (default : LineSearchStrongWolfe()).
    callback : callable *optional*
        Function f(energy, iteration_number) supplied by the user to perform
        in-situ analysis at every iteration step. When being called the
        current energy and iteration_number are passed. (default: None)

44
45
    Attributes
    ----------
46
47
48
    line_searcher : LineSearch
        Function which infers the optimal step size for functional minization
        given a descent direction.
49
    callback : function
50
51
52
53
54
        Function f(energy, iteration_number) supplied by the user to perform
        in-situ analysis at every iteration step. When being called the
        current energy and iteration_number are passed.

    Notes
55
    ------
56
57
58
59
60
61
    The callback function can be used to externally stop the minimization by
    raising a `StopIteration` exception.
    Check `get_descent_direction` of a derived class for information on the
    concrete minization scheme.

    """
62

Martin Reinecke's avatar
Martin Reinecke committed
63
64
    def __init__(self, controller, line_searcher=LineSearchStrongWolfe()):
        super(DescentMinimizer, self).__init__()
65
66

        self.line_searcher = line_searcher
Martin Reinecke's avatar
Martin Reinecke committed
67
        self._controller = controller
68

69
    def __call__(self, energy):
70
        """ Performs the minimization of the provided Energy functional.
71
72
73
74

        Parameters
        ----------
        energy : Energy object
75
76
           Energy object which provides value, gradient and curvature at a
           specific position in parameter space.
77
78
79

        Returns
        -------
80
        energy : Energy object
81
82
83
84
            Latest `energy` of the minimization.
        convergence : integer
            Latest convergence level indicating whether the minimization
            has converged or not.
85

86
87
        Note
        ----
88
89
90
91
92
93
94
        The minimization is stopped if
            * the callback function raises a `StopIteration` exception,
            * a perfectly flat point is reached,
            * according to the line-search the minimum is found,
            * the target convergence level is reached,
            * the iteration limit is reached.

95
96
97
        """

        f_k_minus_1 = None
Martin Reinecke's avatar
Martin Reinecke committed
98
99
100
101
        controller = self._controller
        status = controller.start(energy)
        if status != controller.CONTINUE:
            return E, status
102
103

        while True:
104
            # check if position is at a flat point
Martin Reinecke's avatar
Martin Reinecke committed
105
            if energy.gradient_norm == 0:
106
                self.logger.info("Reached perfectly flat point. Stopping.")
Martin Reinecke's avatar
Martin Reinecke committed
107
                return energy, controller.CONVERGED
108

109
            # current position is encoded in energy object
110
            descent_direction = self.get_descent_direction(energy)
111
112
            # compute the step length, which minimizes energy.value along the
            # search direction
Theo Steininger's avatar
Theo Steininger committed
113
            try:
114
                new_energy = \
Theo Steininger's avatar
Theo Steininger committed
115
116
117
118
119
120
121
                    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
122
                return energy, controller.ERROR
Theo Steininger's avatar
Theo Steininger committed
123

124
            f_k_minus_1 = energy.value
125
126
            # check if new energy value is bigger than old energy value
            if (new_energy.value - energy.value) > 0:
127
128
                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
129
                return energy, controller.ERROR
130

131
            energy = new_energy
Martin Reinecke's avatar
Martin Reinecke committed
132
133
134
135
            status = self._controller.check(energy)
            if status != controller.CONTINUE:
                return energy, status

136
137

    @abc.abstractmethod
138
    def get_descent_direction(self, energy):
139
        raise NotImplementedError