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


Martin Reinecke's avatar
Martin Reinecke committed
29
class LinearOperator(with_metaclass(NiftyMeta, type('NewBase', (Loggable, 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
70
71
72
73
74

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

    """

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

78
79
    @staticmethod
    def _parse_domain(domain):
80
        return utilities.parse_domain(domain)
81

82
    @abc.abstractproperty
83
    def domain(self):
84
        """
Theo Steininger's avatar
Theo Steininger committed
85
86
87
        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
88
89
90
            base class must have this attribute.

        """
Theo Steininger's avatar
Theo Steininger committed
91

92
        raise NotImplementedError
93
94
95

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

        """
Theo Steininger's avatar
Theo Steininger committed
103

104
105
        raise NotImplementedError

106
107
    @abc.abstractproperty
    def unitary(self):
108
109
110
111
112
113
114
        """
        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
115

116
117
        raise NotImplementedError

118
119
120
121
    @property
    def default_spaces(self):
        return self._default_spaces

122
123
124
    def __call__(self, *args, **kwargs):
        return self.times(*args, **kwargs)

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
139
140
        out : Field
            The processed Field living on the target-domain.
141
142
143

        """

144
        spaces = self._check_input_compatibility(x, spaces)
Theo Steininger's avatar
Theo Steininger committed
145
        y = self._times(x, spaces)
146
147
        return y

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
162
163
        out : Field
            The processed Field living on the target-domain.
164
165
166

        """

167
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
168

169
        try:
Theo Steininger's avatar
Theo Steininger committed
170
            y = self._inverse_times(x, spaces)
171
172
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
173
                y = self._adjoint_times(x, spaces)
174
175
            else:
                raise
176
177
        return y

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
192
193
        out : Field
            The processed Field living on the target-domain.
194
195

        """
Theo Steininger's avatar
Theo Steininger committed
196

197
        if self.unitary:
198
            return self.inverse_times(x, spaces)
199

200
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
201

202
        try:
Theo Steininger's avatar
Theo Steininger committed
203
            y = self._adjoint_times(x, spaces)
204
205
        except(NotImplementedError):
            if (self.unitary):
Theo Steininger's avatar
Theo Steininger committed
206
                y = self._inverse_times(x, spaces)
207
208
            else:
                raise
209
210
        return y

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
225
226
        out : Field
            The processed Field living on the target-domain.
227

Theo Steininger's avatar
Theo Steininger committed
228
229
230
231
232
        Notes
        -----
        If the operator has an `inverse` then the inverse adjoint is identical
        to the adjoint inverse. We provide both names for convenience.

233
        See Also
Theo Steininger's avatar
Theo Steininger committed
234
        --------
235
236

        """
237

238
        spaces = self._check_input_compatibility(x, spaces)
239

240
        try:
Theo Steininger's avatar
Theo Steininger committed
241
            y = self._adjoint_inverse_times(x, spaces)
242
243
        except(NotImplementedError):
            if self.unitary:
Theo Steininger's avatar
Theo Steininger committed
244
                y = self._times(x, spaces)
245
246
            else:
                raise
247
248
        return y

Theo Steininger's avatar
Theo Steininger committed
249
250
    def inverse_adjoint_times(self, x, spaces=None):
        return self.adjoint_inverse_times(x, spaces)
251

252
    def _times(self, x, spaces):
253
254
        raise NotImplementedError(
            "no generic instance method 'times'.")
255

256
    def _adjoint_times(self, x, spaces):
257
258
        raise NotImplementedError(
            "no generic instance method 'adjoint_times'.")
259

260
    def _inverse_times(self, x, spaces):
261
262
        raise NotImplementedError(
            "no generic instance method 'inverse_times'.")
263

264
    def _adjoint_inverse_times(self, x, spaces):
265
266
        raise NotImplementedError(
            "no generic instance method 'adjoint_inverse_times'.")
267

268
    def _check_input_compatibility(self, x, spaces, inverse=False):
269
        if not isinstance(x, Field):
270
            raise ValueError(
Theo Steininger's avatar
Theo Steininger committed
271
                "supplied object is not a `Field`.")
272

273
274
275
276
277
        if spaces is None and self.default_spaces is not None:
            if not inverse:
                spaces = self.default_spaces
            else:
                spaces = self.default_spaces[::-1]
278

279
280
281
282
283
284
285
        # 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
286
        #   be applied to certain spaces in the domain-tuple of x.
287
288
289
        # 2. Case:
        #   The domains of self and x match completely.

290
291
292
293
294
        if not inverse:
            self_domain = self.domain
        else:
            self_domain = self.target

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

307
        return spaces
308
309
310

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