linear_operator.py 9.7 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
from keepers import Loggable
24
25
from ...field import Field
from ... import nifty_utilities as utilities
Martin Reinecke's avatar
Martin Reinecke committed
26
from future.utils import with_metaclass
27
28


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

33
34
35
36
    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
37
    PropagatorOperator, ComposedOperator) are derived.
38
39
40

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

    Attributes
    ----------
Theo Steininger's avatar
Theo Steininger committed
47
48
49
50
    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.
51
    unitary : boolean
Theo Steininger's avatar
Theo Steininger committed
52
        Indicates whether the Operator is unitary or not.
53
54
55
56
57
58
59
60
61
62
63

    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
64
65
66
    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.
67
68
69
70
71
72
73
74
75

    See Also
    --------
    EndomorphicOperator, ProjectionOperator,
    DiagonalOperator, SmoothingOperator, ResponseOperator,
    PropagatorOperator, ComposedOperator

    """

76
    def __init__(self, default_spaces=None):
77
        self._default_spaces = default_spaces
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
    def copy(self, **kwargs):
        class EmptyCopy(self.__class__):
            def __init__(self):
                pass

        result = EmptyCopy()
        result.__class__ = self.__class__
        result = self._add_attributes_to_copy(result, **kwargs)
        return result

    def _add_attributes_to_copy(self, copy, **kwargs):
        copy._default_spaces = self.default_spaces
        return copy

93
94
    @staticmethod
    def _parse_domain(domain):
95
        return utilities.parse_domain(domain)
96

97
    @abc.abstractproperty
98
    def domain(self):
99
        """
Theo Steininger's avatar
Theo Steininger committed
100
101
102
        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
103
104
105
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
106

107
        raise NotImplementedError
108
109
110

    @abc.abstractproperty
    def target(self):
111
        """
Theo Steininger's avatar
Theo Steininger committed
112
113
114
        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
115
116
117
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
118

119
120
        raise NotImplementedError

121
122
    @abc.abstractproperty
    def unitary(self):
123
124
125
126
127
128
129
        """
        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
130

131
132
        raise NotImplementedError

133
134
135
136
    @property
    def default_spaces(self):
        return self._default_spaces

137
138
139
    def __call__(self, *args, **kwargs):
        return self.times(*args, **kwargs)

Theo Steininger's avatar
Theo Steininger committed
140
    def times(self, x, spaces=None):
141
142
143
144
145
146
        """ 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
147
148
149
150
        x : Field
            The input Field.
        spaces : tuple of ints
            Defines on which space(s) of the given Field the Operator acts.
151
152
153

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

        """

159
        spaces = self._check_input_compatibility(x, spaces)
Theo Steininger's avatar
Theo Steininger committed
160
        y = self._times(x, spaces)
161
162
        return y

Theo Steininger's avatar
Theo Steininger committed
163
    def inverse_times(self, x, spaces=None):
164
165
166
167
168
169
        """ 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
170
171
172
173
        x : Field
            The input Field.
        spaces : tuple of ints
            Defines on which space(s) of the given Field the Operator acts.
174
175
176

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
177
178
        out : Field
            The processed Field living on the target-domain.
179
180
181

        """

182
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
183

184
        try:
Theo Steininger's avatar
Theo Steininger committed
185
            y = self._inverse_times(x, spaces)
186
187
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
188
                y = self._adjoint_times(x, spaces)
189
190
            else:
                raise
191
192
        return y

Theo Steininger's avatar
Theo Steininger committed
193
    def adjoint_times(self, x, spaces=None):
194
195
196
197
198
199
        """ 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
200
        x : Field
201
            applies the Operator to the given Field
Theo Steininger's avatar
Theo Steininger committed
202
        spaces : tuple of ints
203
204
205
206
            defines on which space of the given Field the Operator acts

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
207
208
        out : Field
            The processed Field living on the target-domain.
209
210

        """
Theo Steininger's avatar
Theo Steininger committed
211

212
        if self.unitary:
213
            return self.inverse_times(x, spaces)
214

215
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
216

217
        try:
Theo Steininger's avatar
Theo Steininger committed
218
            y = self._adjoint_times(x, spaces)
219
220
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
221
                y = self._inverse_times(x, spaces)
222
223
            else:
                raise
224
225
        return y

Theo Steininger's avatar
Theo Steininger committed
226
    def adjoint_inverse_times(self, x, spaces=None):
227
228
229
230
231
232
        """ 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
233
        x : Field
234
            applies the Operator to the given Field
Theo Steininger's avatar
Theo Steininger committed
235
        spaces : tuple of ints
236
237
238
239
            defines on which space of the given Field the Operator acts

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
240
241
        out : Field
            The processed Field living on the target-domain.
242

Theo Steininger's avatar
Theo Steininger committed
243
244
245
246
247
        Notes
        -----
        If the operator has an `inverse` then the inverse adjoint is identical
        to the adjoint inverse. We provide both names for convenience.

248
        See Also
Theo Steininger's avatar
Theo Steininger committed
249
        --------
250
251

        """
252

253
        spaces = self._check_input_compatibility(x, spaces)
254

255
        try:
Theo Steininger's avatar
Theo Steininger committed
256
            y = self._adjoint_inverse_times(x, spaces)
257
258
        except(NotImplementedError):
            if self.unitary:
Theo Steininger's avatar
Theo Steininger committed
259
                y = self._times(x, spaces)
260
261
            else:
                raise
262
263
        return y

Theo Steininger's avatar
Theo Steininger committed
264
265
    def inverse_adjoint_times(self, x, spaces=None):
        return self.adjoint_inverse_times(x, spaces)
266

267
    def _times(self, x, spaces):
268
269
        raise NotImplementedError(
            "no generic instance method 'times'.")
270

271
    def _adjoint_times(self, x, spaces):
272
273
        raise NotImplementedError(
            "no generic instance method 'adjoint_times'.")
274

275
    def _inverse_times(self, x, spaces):
276
277
        raise NotImplementedError(
            "no generic instance method 'inverse_times'.")
278

279
    def _adjoint_inverse_times(self, x, spaces):
280
281
        raise NotImplementedError(
            "no generic instance method 'adjoint_inverse_times'.")
282

283
    def _check_input_compatibility(self, x, spaces, inverse=False):
284
        if not isinstance(x, Field):
285
            raise ValueError(
Theo Steininger's avatar
Theo Steininger committed
286
                "supplied object is not a `Field`.")
287

288
289
290
291
292
        if spaces is None and self.default_spaces is not None:
            if not inverse:
                spaces = self.default_spaces
            else:
                spaces = self.default_spaces[::-1]
293

294
295
296
297
298
299
300
        # sanitize the `spaces` and `types` input
        spaces = utilities.cast_axis_to_tuple(spaces, len(x.domain))

        # 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
301
        #   be applied to certain spaces in the domain-tuple of x.
302
303
304
        # 2. Case:
        #   The domains of self and x match completely.

305
306
307
308
309
        if not inverse:
            self_domain = self.domain
        else:
            self_domain = self.target

310
        if spaces is None:
311
            if self_domain != x.domain:
312
313
314
                raise ValueError(
                    "The operator's and and field's domains don't "
                    "match.")
315
        else:
316
317
            for i, space_index in enumerate(spaces):
                if x.domain[space_index] != self_domain[i]:
318
319
320
                    raise ValueError(
                        "The operator's and and field's domains don't "
                        "match.")
321

322
        return spaces
323
324
325

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