energy.py 6.18 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

Martin Reinecke's avatar
Martin Reinecke committed
19
20
from __future__ import absolute_import, division, print_function
from ..compat import *
21
import numpy as np
Philipp Arras's avatar
Philipp Arras committed
22
23
from ..field import Field
from ..utilities import NiftyMetaBase, memo
24

25

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

29
   The Energy object is an implementation of a scalar function including its
Martin Reinecke's avatar
Martin Reinecke committed
30
   gradient and metric at some position.
31
32
33

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

    Notes
    -----
39
    An instance of the Energy class is defined at a certain location. If one
Martin Reinecke's avatar
Martin Reinecke committed
40
    is interested in the value, gradient or metric of the abstract energy
41
42
43
    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
Martin Reinecke's avatar
Martin Reinecke committed
44
    safely be reused for e.g. the value or the metric.
45

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

Martin Reinecke's avatar
Martin Reinecke committed
49
    See Also
50
51
    --------
    memo
52
53

    """
54

55
    def __init__(self, position):
56
        super(Energy, self).__init__()
57
        self._position = position
58
59

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

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

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

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

        The Field location in parameter space where value, gradient and
Martin Reinecke's avatar
Martin Reinecke committed
80
        metric are evaluated.
81
        """
82
83
        return self._position

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

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

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

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

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

    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
132

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

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

145
146
147
148
149
    def __rmul__(self, factor):
        return self.__mul__(factor)

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

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

    def __neg__(self):
        from .energy_sum import EnergySum
        return EnergySum.make([self], [-1.])
163
164


Martin Reinecke's avatar
Martin Reinecke committed
165
class MetricInversionEnabler(Energy):
166
    def __init__(self, ene, controller, preconditioner):
Martin Reinecke's avatar
Martin Reinecke committed
167
        super(MetricInversionEnabler, self).__init__(ene.position)
168
169
170
171
172
173
174
        self._energy = ene
        self._controller = controller
        self._preconditioner = preconditioner

    def at(self, position):
        if self._position.isSubsetOf(position):
            return self
Martin Reinecke's avatar
Martin Reinecke committed
175
        return MetricInversionEnabler(
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
            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
Martin Reinecke's avatar
Martin Reinecke committed
191
    def metric(self):
192
193
        from ..operators.linear_operator import LinearOperator
        from ..operators.inversion_enabler import InversionEnabler
Martin Reinecke's avatar
Martin Reinecke committed
194
        curv = self._energy.metric
195
196
197
198
199
        if self._preconditioner is None:
            precond = None
        elif isinstance(self._preconditioner, LinearOperator):
            precond = self._preconditioner
        elif isinstance(self._preconditioner, Energy):
Martin Reinecke's avatar
Martin Reinecke committed
200
            precond = self._preconditioner.at(self.position).metric
201
202
203
204
        return InversionEnabler(curv, self._controller, precond)

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