linear_operator.py 9.13 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

Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
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
class LinearOperator(with_metaclass(
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
30
        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
    def __init__(self, default_spaces=None):
71
        self._default_spaces = default_spaces
72

73
74
    @staticmethod
    def _parse_domain(domain):
75
        return utilities.parse_domain(domain)
76

77
    @abc.abstractproperty
78
    def domain(self):
79
        """
Theo Steininger's avatar
Theo Steininger committed
80
81
82
        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
83
84
85
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
86

87
        raise NotImplementedError
88
89
90

    @abc.abstractproperty
    def target(self):
91
        """
Theo Steininger's avatar
Theo Steininger committed
92
93
94
        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
95
96
97
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
98

99
100
        raise NotImplementedError

101
102
    @abc.abstractproperty
    def unitary(self):
103
104
105
106
107
108
109
        """
        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
110

111
112
        raise NotImplementedError

113
114
115
116
    @property
    def default_spaces(self):
        return self._default_spaces

117
118
119
    def __call__(self, *args, **kwargs):
        return self.times(*args, **kwargs)

Theo Steininger's avatar
Theo Steininger committed
120
    def times(self, x, spaces=None):
121
122
123
124
125
126
        """ 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
127
128
129
130
        x : Field
            The input Field.
        spaces : tuple of ints
            Defines on which space(s) of the given Field the Operator acts.
131
132
133

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
134
135
        out : Field
            The processed Field living on the target-domain.
136
137
138

        """

139
        spaces = self._check_input_compatibility(x, spaces)
Theo Steininger's avatar
Theo Steininger committed
140
        y = self._times(x, spaces)
141
142
        return y

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
157
158
        out : Field
            The processed Field living on the target-domain.
159
160
161

        """

162
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
163

164
        try:
Theo Steininger's avatar
Theo Steininger committed
165
            y = self._inverse_times(x, spaces)
166
167
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
168
                y = self._adjoint_times(x, spaces)
169
170
            else:
                raise
171
172
        return y

Theo Steininger's avatar
Theo Steininger committed
173
    def adjoint_times(self, x, spaces=None):
174
175
176
177
178
179
        """ 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
180
        x : Field
181
            applies the Operator to the given Field
Theo Steininger's avatar
Theo Steininger committed
182
        spaces : tuple of ints
183
184
185
186
            defines on which space of the given Field the Operator acts

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
187
188
        out : Field
            The processed Field living on the target-domain.
189
190

        """
Theo Steininger's avatar
Theo Steininger committed
191

192
        if self.unitary:
193
            return self.inverse_times(x, spaces)
194

195
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
196

197
        try:
Theo Steininger's avatar
Theo Steininger committed
198
            y = self._adjoint_times(x, spaces)
199
200
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
201
                y = self._inverse_times(x, spaces)
202
203
            else:
                raise
204
205
        return y

Theo Steininger's avatar
Theo Steininger committed
206
    def adjoint_inverse_times(self, x, spaces=None):
207
208
209
210
211
212
        """ 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
213
        x : Field
214
            applies the Operator to the given Field
Theo Steininger's avatar
Theo Steininger committed
215
        spaces : tuple of ints
216
217
218
219
            defines on which space of the given Field the Operator acts

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
220
221
        out : Field
            The processed Field living on the target-domain.
222

Theo Steininger's avatar
Theo Steininger committed
223
224
225
226
227
        Notes
        -----
        If the operator has an `inverse` then the inverse adjoint is identical
        to the adjoint inverse. We provide both names for convenience.

228
        See Also
Theo Steininger's avatar
Theo Steininger committed
229
        --------
230
231

        """
232

233
        spaces = self._check_input_compatibility(x, spaces)
234

235
        try:
Theo Steininger's avatar
Theo Steininger committed
236
            y = self._adjoint_inverse_times(x, spaces)
237
238
        except(NotImplementedError):
            if self.unitary:
Theo Steininger's avatar
Theo Steininger committed
239
                y = self._times(x, spaces)
240
241
            else:
                raise
242
243
        return y

Theo Steininger's avatar
Theo Steininger committed
244
245
    def inverse_adjoint_times(self, x, spaces=None):
        return self.adjoint_inverse_times(x, spaces)
246

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

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

255
    def _inverse_times(self, x, spaces):
256
257
        raise NotImplementedError(
            "no generic instance method 'inverse_times'.")
258

259
    def _adjoint_inverse_times(self, x, spaces):
260
261
        raise NotImplementedError(
            "no generic instance method 'adjoint_inverse_times'.")
262

263
    def _check_input_compatibility(self, x, spaces, inverse=False):
264
        if not isinstance(x, Field):
265
            raise ValueError(
Theo Steininger's avatar
Theo Steininger committed
266
                "supplied object is not a `Field`.")
267

268
269
270
271
272
        if spaces is None and self.default_spaces is not None:
            if not inverse:
                spaces = self.default_spaces
            else:
                spaces = self.default_spaces[::-1]
273

274
275
276
277
278
279
280
        # 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
281
        #   be applied to certain spaces in the domain-tuple of x.
282
283
284
        # 2. Case:
        #   The domains of self and x match completely.

285
286
287
288
289
        if not inverse:
            self_domain = self.domain
        else:
            self_domain = self.target

290
        if spaces is None:
291
            if self_domain != x.domain:
292
293
294
                raise ValueError(
                    "The operator's and and field's domains don't "
                    "match.")
295
        else:
296
297
            for i, space_index in enumerate(spaces):
                if x.domain[space_index] != self_domain[i]:
298
299
300
                    raise ValueError(
                        "The operator's and and field's domains don't "
                        "match.")
301

302
        return spaces
303
304
305

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