linear_operator.py 8.88 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 builtins import str
20
import abc
21
from ...nifty_meta import NiftyMeta
22

23
24
from ...field import Field
from ... import nifty_utilities as utilities
Martin Reinecke's avatar
Martin Reinecke committed
25
from future.utils import with_metaclass
26
27


28
class LinearOperator(with_metaclass(
Martin Reinecke's avatar
Martin Reinecke committed
29
        NiftyMeta, type('NewBase', (object,), {}))):
30
    """NIFTY base class for linear operators.
Theo Steininger's avatar
Theo Steininger committed
31

32
33
34
35
    The base NIFTY operator class is an abstract class from which
    other specific operator subclasses, including those preimplemented
    in NIFTY (e.g. the EndomorphicOperator, ProjectionOperator,
    DiagonalOperator, SmoothingOperator, ResponseOperator,
Theo Steininger's avatar
Theo Steininger committed
36
    PropagatorOperator, ComposedOperator) are derived.
37
38
39

    Parameters
    ----------
Theo Steininger's avatar
Theo Steininger committed
40
41
42
    default_spaces : tuple of ints *optional*
        Defines on which space(s) of a given field the Operator acts by
        default (default: None)
43
44
45

    Attributes
    ----------
Theo Steininger's avatar
Theo Steininger committed
46
47
48
49
    domain : tuple of DomainObjects, i.e. Spaces and FieldTypes
        The domain on which the Operator's input Field lives.
    target : tuple of DomainObjects, i.e. Spaces and FieldTypes
        The domain in which the Operators result lives.
50
    unitary : boolean
Theo Steininger's avatar
Theo Steininger committed
51
        Indicates whether the Operator is unitary or not.
52
53
54
55
56
57
58
59
60
61
62

    Raises
    ------
    NotImplementedError
        Raised if
            * domain is not defined
            * target is not defined
            * unitary is not set to (True/False)

    Notes
    -----
Theo Steininger's avatar
Theo Steininger committed
63
64
65
    All Operators wihtin NIFTy are linear and must therefore be a subclasses of
    the LinearOperator. A LinearOperator must have the attributes domain,
    target and unitary to be properly defined.
66
67
68

    """

69
    def __init__(self, default_spaces=None):
70
        self._default_spaces = default_spaces
71

72
    @abc.abstractproperty
73
    def domain(self):
74
        """
Theo Steininger's avatar
Theo Steininger committed
75
76
77
        domain : tuple of DomainObjects, i.e. Spaces and FieldTypes
            The domain on which the Operator's input Field lives.
            Every Operator which inherits from the abstract LinearOperator
78
79
80
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
81

82
        raise NotImplementedError
83
84
85

    @abc.abstractproperty
    def target(self):
86
        """
Theo Steininger's avatar
Theo Steininger committed
87
88
89
        target : tuple of DomainObjects, i.e. Spaces and FieldTypes
            The domain on which the Operator's output Field lives.
            Every Operator which inherits from the abstract LinearOperator
90
91
92
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
93

94
95
        raise NotImplementedError

96
97
    @abc.abstractproperty
    def unitary(self):
98
99
100
101
102
103
104
        """
        unitary : boolean
            States whether the Operator is unitary or not.
            Every Operator which inherits from the abstract LinearOperator
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
105

106
107
        raise NotImplementedError

108
109
110
111
    @property
    def default_spaces(self):
        return self._default_spaces

Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
112
113
    def __call__(self, x, spaces=None):
        return self.times(x, spaces)
114

Theo Steininger's avatar
Theo Steininger committed
115
    def times(self, x, spaces=None):
116
117
118
119
120
121
        """ Applies the Operator to a given Field.

        Operator and Field have to live over the same domain.

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
122
123
124
125
        x : Field
            The input Field.
        spaces : tuple of ints
            Defines on which space(s) of the given Field the Operator acts.
126
127
128

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
129
130
        out : Field
            The processed Field living on the target-domain.
131
132
133

        """

134
        spaces = self._check_input_compatibility(x, spaces)
Theo Steininger's avatar
Theo Steininger committed
135
        y = self._times(x, spaces)
136
137
        return y

Theo Steininger's avatar
Theo Steininger committed
138
    def inverse_times(self, x, spaces=None):
139
140
141
142
143
144
        """ Applies the inverse-Operator to a given Field.

        Operator and Field have to live over the same domain.

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
145
146
147
148
        x : Field
            The input Field.
        spaces : tuple of ints
            Defines on which space(s) of the given Field the Operator acts.
149
150
151

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
152
153
        out : Field
            The processed Field living on the target-domain.
154
155
156

        """

157
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
158

159
        try:
Theo Steininger's avatar
Theo Steininger committed
160
            y = self._inverse_times(x, spaces)
161
162
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
163
                y = self._adjoint_times(x, spaces)
164
165
            else:
                raise
166
167
        return y

Theo Steininger's avatar
Theo Steininger committed
168
    def adjoint_times(self, x, spaces=None):
169
170
171
172
173
174
        """ Applies the adjoint-Operator to a given Field.

        Operator and Field have to live over the same domain.

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
175
        x : Field
176
            applies the Operator to the given Field
Theo Steininger's avatar
Theo Steininger committed
177
        spaces : tuple of ints
178
179
180
181
            defines on which space of the given Field the Operator acts

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
182
183
        out : Field
            The processed Field living on the target-domain.
184
185

        """
Theo Steininger's avatar
Theo Steininger committed
186

187
        if self.unitary:
188
            return self.inverse_times(x, spaces)
189

190
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
191

192
        try:
Theo Steininger's avatar
Theo Steininger committed
193
            y = self._adjoint_times(x, spaces)
194
195
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
196
                y = self._inverse_times(x, spaces)
197
198
            else:
                raise
199
200
        return y

Theo Steininger's avatar
Theo Steininger committed
201
    def adjoint_inverse_times(self, x, spaces=None):
202
203
204
205
206
207
        """ Applies the adjoint-inverse Operator to a given Field.

        Operator and Field have to live over the same domain.

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
208
        x : Field
209
            applies the Operator to the given Field
Theo Steininger's avatar
Theo Steininger committed
210
        spaces : tuple of ints
211
212
213
214
            defines on which space of the given Field the Operator acts

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
215
216
        out : Field
            The processed Field living on the target-domain.
217

Theo Steininger's avatar
Theo Steininger committed
218
219
220
221
222
        Notes
        -----
        If the operator has an `inverse` then the inverse adjoint is identical
        to the adjoint inverse. We provide both names for convenience.

223
        """
224

225
        spaces = self._check_input_compatibility(x, spaces)
226

227
        try:
Theo Steininger's avatar
Theo Steininger committed
228
            y = self._adjoint_inverse_times(x, spaces)
229
230
        except(NotImplementedError):
            if self.unitary:
Theo Steininger's avatar
Theo Steininger committed
231
                y = self._times(x, spaces)
232
233
            else:
                raise
234
235
        return y

Theo Steininger's avatar
Theo Steininger committed
236
237
    def inverse_adjoint_times(self, x, spaces=None):
        return self.adjoint_inverse_times(x, spaces)
238

239
    def _times(self, x, spaces):
240
241
        raise NotImplementedError(
            "no generic instance method 'times'.")
242

243
    def _adjoint_times(self, x, spaces):
244
245
        raise NotImplementedError(
            "no generic instance method 'adjoint_times'.")
246

247
    def _inverse_times(self, x, spaces):
248
249
        raise NotImplementedError(
            "no generic instance method 'inverse_times'.")
250

251
    def _adjoint_inverse_times(self, x, spaces):
252
253
        raise NotImplementedError(
            "no generic instance method 'adjoint_inverse_times'.")
254

255
    def _check_input_compatibility(self, x, spaces, inverse=False):
256
        if not isinstance(x, Field):
Martin Reinecke's avatar
updates    
Martin Reinecke committed
257
            raise ValueError("supplied object is not a `Field`.")
258

259
260
261
262
263
        if spaces is None and self.default_spaces is not None:
            if not inverse:
                spaces = self.default_spaces
            else:
                spaces = self.default_spaces[::-1]
264

Martin Reinecke's avatar
Martin Reinecke committed
265
266
267
        # sanitize the `spaces` input
        if spaces is not None:
            spaces = utilities.cast_iseq_to_tuple(spaces)
268
269
270
271
272

        # if the operator's domain is set to something, there are two valid
        # cases:
        # 1. Case:
        #   The user specifies with `spaces` that the operators domain should
273
        #   be applied to certain spaces in the domain-tuple of x.
274
275
276
        # 2. Case:
        #   The domains of self and x match completely.

Martin Reinecke's avatar
updates    
Martin Reinecke committed
277
        self_domain = self.target if inverse else self.domain
278

279
        if spaces is None:
280
            if self_domain != x.domain:
Martin Reinecke's avatar
Martin Reinecke committed
281
282
                raise ValueError("The operator's and and field's domains "
                                 "don't match.")
283
        else:
284
285
            for i, space_index in enumerate(spaces):
                if x.domain[space_index] != self_domain[i]:
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
286
287
                    raise ValueError("The operator's and and field's domains "
                                     "don't match.")
288

289
        return spaces
290
291
292

    def __repr__(self):
        return str(self.__class__)