linear_operator.py 9.23 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# NIFTy
# Copyright (C) 2017  Theo Steininger
#
# Author: Theo Steininger
#
# 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/>.
18

19
import abc
20
from nifty.nifty_meta import NiftyMeta
21

22
from keepers import Loggable
23
24
25
26
from nifty.field import Field
import nifty.nifty_utilities as utilities


theos's avatar
theos committed
27
class LinearOperator(Loggable, object):
28
    """NIFTY base class for linear operators.
Theo Steininger's avatar
Theo Steininger committed
29

30
31
32
33
    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
34
    PropagatorOperator, ComposedOperator) are derived.
35
36
37

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

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

    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
61
62
63
    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.
64
65
66
67
68
69
70
71
72

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

    """

73
    __metaclass__ = NiftyMeta
74

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

    def _parse_domain(self, domain):
79
        return utilities.parse_domain(domain)
80

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

        """
Theo Steininger's avatar
Theo Steininger committed
90

91
        raise NotImplementedError
92
93
94

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

        """
Theo Steininger's avatar
Theo Steininger committed
102

103
104
        raise NotImplementedError

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

115
116
        raise NotImplementedError

117
118
119
120
121
122
123
124
    @property
    def default_spaces(self):
        return self._default_spaces

    @default_spaces.setter
    def default_spaces(self, spaces):
        self._default_spaces = utilities.cast_axis_to_tuple(spaces)

125
126
127
    def __call__(self, *args, **kwargs):
        return self.times(*args, **kwargs)

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
142
143
        out : Field
            The processed Field living on the target-domain.
144
145
146

        """

147
        spaces = self._check_input_compatibility(x, spaces)
Theo Steininger's avatar
Theo Steininger committed
148
        y = self._times(x, spaces)
149
150
        return y

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
165
166
        out : Field
            The processed Field living on the target-domain.
167
168
169

        """

170
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
171

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

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
195
196
        out : Field
            The processed Field living on the target-domain.
197
198

        """
Theo Steininger's avatar
Theo Steininger committed
199

200
        if self.unitary:
201
            return self.inverse_times(x, spaces)
202

203
        spaces = self._check_input_compatibility(x, spaces, inverse=True)
204

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

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

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
228
229
        out : Field
            The processed Field living on the target-domain.
230

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

236
        See Also
Theo Steininger's avatar
Theo Steininger committed
237
        --------
238
239

        """
240

241
        spaces = self._check_input_compatibility(x, spaces)
242

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

Theo Steininger's avatar
Theo Steininger committed
252
253
    def inverse_adjoint_times(self, x, spaces=None):
        return self.adjoint_inverse_times(x, spaces)
254

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

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

263
    def _inverse_times(self, x, spaces):
264
265
        raise NotImplementedError(
            "no generic instance method 'inverse_times'.")
266

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

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

275
    def _check_input_compatibility(self, x, spaces, inverse=False):
276
        if not isinstance(x, Field):
277
            raise ValueError(
Theo Steininger's avatar
Theo Steininger committed
278
                "supplied object is not a `Field`.")
279

280
281
282
        if spaces is None:
            spaces = self.default_spaces

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

294
295
296
297
298
        if not inverse:
            self_domain = self.domain
        else:
            self_domain = self.target

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

311
        return spaces
312
313
314

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