energy.py 6.14 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
#
Martin Reinecke's avatar
Martin Reinecke committed
14
# Copyright(C) 2013-2018 Max-Planck-Society
Theo Steininger's avatar
Theo Steininger committed
15
16
17
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
18

19
import numpy as np
Philipp Arras's avatar
Philipp Arras committed
20
21
from ..field import Field
from ..utilities import NiftyMetaBase, memo
22

23

Martin Reinecke's avatar
Martin Reinecke committed
24
class Energy(NiftyMetaBase()):
25
    """ Provides the functional used by minimization schemes.
26

27
28
   The Energy object is an implementation of a scalar function including its
   gradient and curvature at some position.
29
30
31

    Parameters
    ----------
32
33
    position : Field
        The input parameter of the scalar function.
34
35
36

    Notes
    -----
37
38
39
40
41
42
    An instance of the Energy class is defined at a certain location. If one
    is interested in the value, gradient or curvature of the abstract energy
    functional one has to 'jump' to the new position using the `at` method.
    This method returns a new energy instance residing at the new position. By
    this approach, intermediate results from computing e.g. the gradient can
    safely be reused for e.g. the value or the curvature.
43

44
45
46
    Memorizing the evaluations of some quantities (using the memo decorator)
    minimizes the computational effort for multiple calls.

Martin Reinecke's avatar
Martin Reinecke committed
47
    See Also
48
49
    --------
    memo
50
51

    """
52

53
    def __init__(self, position):
54
        super(Energy, self).__init__()
Martin Reinecke's avatar
Martin Reinecke committed
55
        self._position = position.lock()
56
57

    def at(self, position):
Martin Reinecke's avatar
Martin Reinecke committed
58
        """ Returns a new Energy object, initialized at `position`.
59
60
61
62

        Parameters
        ----------
        position : Field
Martin Reinecke's avatar
Martin Reinecke committed
63
            Location in parameter space for the new Energy object.
64
65
66

        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
67
        Energy
68
69
            Energy object at new position.
        """
70
71
        return self.__class__(position)

72
73
    @property
    def position(self):
74
        """
Martin Reinecke's avatar
Martin Reinecke committed
75
76
77
78
        Field : selected location in parameter space.

        The Field location in parameter space where value, gradient and
        curvature are evaluated.
79
        """
80
81
        return self._position

82
83
    @property
    def value(self):
84
        """
Martin Reinecke's avatar
Martin Reinecke committed
85
86
        float : value of the functional.

Martin Reinecke's avatar
Martin Reinecke committed
87
            The value of the energy functional at given `position`.
88
        """
89
90
91
92
        raise NotImplementedError

    @property
    def gradient(self):
93
        """
Martin Reinecke's avatar
Martin Reinecke committed
94
        Field : The gradient at given `position`.
95
        """
96
97
        raise NotImplementedError

Martin Reinecke's avatar
Martin Reinecke committed
98
99
100
101
    @property
    @memo
    def gradient_norm(self):
        """
Martin Reinecke's avatar
Martin Reinecke committed
102
        float : L2-norm of the gradient at given `position`.
Martin Reinecke's avatar
Martin Reinecke committed
103
104
105
        """
        return self.gradient.norm()

106
107
    @property
    def curvature(self):
108
        """
Martin Reinecke's avatar
Martin Reinecke committed
109
        LinearOperator : implicitly defined curvature.
Martin Reinecke's avatar
Martin Reinecke committed
110
111
            A positive semi-definite operator or function describing the
            curvature of the potential at the given `position`.
112
        """
113
        raise NotImplementedError
Martin Reinecke's avatar
stage 1  
Martin Reinecke committed
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

    def longest_step(self, dir):
        """Returns the longest allowed step size along `dir`

        Parameters
        ----------
        dir : Field
            the search direction

        Returns
        -------
        float or None
            the longest allowed step when starting from `self.position` along
            `dir`. If None, the step size is not limited.
        """
        return None
Philipp Arras's avatar
Philipp Arras committed
130

131
    def makeInvertible(self, controller, preconditioner=None):
Martin Reinecke's avatar
fixes  
Martin Reinecke committed
132
        from .iteration_controller import IterationController
133
134
135
136
        if not isinstance(controller, IterationController):
            raise TypeError
        return CurvatureInversionEnabler(self, controller, preconditioner)

137
138
    def __mul__(self, factor):
        from .energy_sum import EnergySum
Philipp Arras's avatar
Philipp Arras committed
139
140
141
        if isinstance(factor, (float, int)):
            return EnergySum.make([self], [factor])
        return NotImplemented
Philipp Arras's avatar
Philipp Arras committed
142

143
144
145
146
147
    def __rmul__(self, factor):
        return self.__mul__(factor)

    def __add__(self, other):
        from .energy_sum import EnergySum
Philipp Arras's avatar
Philipp Arras committed
148
149
150
        if isinstance(other, Energy):
            return EnergySum.make([self, other])
        return NotImplemented
151
152
153

    def __sub__(self, other):
        from .energy_sum import EnergySum
Philipp Arras's avatar
Philipp Arras committed
154
155
156
        if isinstance(other, Energy):
            return EnergySum.make([self, other], [1., -1.])
        return NotImplemented
157
158
159
160

    def __neg__(self):
        from .energy_sum import EnergySum
        return EnergySum.make([self], [-1.])
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202


class CurvatureInversionEnabler(Energy):
    def __init__(self, ene, controller, preconditioner):
        super(CurvatureInversionEnabler, self).__init__(ene.position)
        self._energy = ene
        self._controller = controller
        self._preconditioner = preconditioner

    def at(self, position):
        if self._position.isSubsetOf(position):
            return self
        return CurvatureInversionEnabler(
            self._energy.at(position), self._controller, self._preconditioner)

    @property
    def position(self):
        return self._energy.position

    @property
    def value(self):
        return self._energy.value

    @property
    def gradient(self):
        return self._energy.gradient

    @property
    def curvature(self):
        from ..operators.linear_operator import LinearOperator
        from ..operators.inversion_enabler import InversionEnabler
        curv = self._energy.curvature
        if self._preconditioner is None:
            precond = None
        elif isinstance(self._preconditioner, LinearOperator):
            precond = self._preconditioner
        elif isinstance(self._preconditioner, Energy):
            precond = self._preconditioner.at(self.position).curvature
        return InversionEnabler(curv, self._controller, precond)

    def longest_step(self, dir):
        return self._energy.longest_step(dir)