linear_operator.py 6.46 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
    The base NIFTY operator class is an abstract class from which
33
    other specific operator subclasses are derived.
34
35
36
37


    Attributes
    ----------
Theo Steininger's avatar
Theo Steininger committed
38
39
40
41
    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.
42
    unitary : boolean
Theo Steininger's avatar
Theo Steininger committed
43
        Indicates whether the Operator is unitary or not.
44
45
46
47
48
49
50
51
52
53

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

54
55
    def __init__(self):
        pass
56

57
    @abc.abstractproperty
58
    def domain(self):
59
        """
Theo Steininger's avatar
Theo Steininger committed
60
61
62
        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
63
64
65
            base class must have this attribute.

        """
66
        raise NotImplementedError
67
68
69

    @abc.abstractproperty
    def target(self):
70
        """
Theo Steininger's avatar
Theo Steininger committed
71
72
73
        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
74
75
            base class must have this attribute.
        """
76
77
        raise NotImplementedError

78
79
    @abc.abstractproperty
    def unitary(self):
80
81
82
83
84
85
        """
        unitary : boolean
            States whether the Operator is unitary or not.
            Every Operator which inherits from the abstract LinearOperator
            base class must have this attribute.
        """
86
87
        raise NotImplementedError

88
89
    def __call__(self, x):
        return self.times(x)
90

91
    def times(self, x):
92
93
94
95
96
97
        """ 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
98
99
        x : Field
            The input Field.
100
101
102

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
103
104
        out : Field
            The processed Field living on the target-domain.
105
        """
106
107
        self._check_input_compatibility(x)
        return self._times(x)
108

109
    def inverse_times(self, x):
110
111
112
113
114
115
        """ 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
116
117
        x : Field
            The input Field.
118
119
120

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
121
122
        out : Field
            The processed Field living on the target-domain.
123
        """
124
        self._check_input_compatibility(x, inverse=True)
125
        try:
126
            y = self._inverse_times(x)
127
128
        except(NotImplementedError):
            if (self.unitary):
129
                y = self._adjoint_times(x)
130
131
            else:
                raise
132
133
        return y

134
    def adjoint_times(self, x):
135
136
137
138
139
140
        """ 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
141
        x : Field
142
143
144
145
            applies the Operator to the given Field

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
146
147
        out : Field
            The processed Field living on the target-domain.
148
        """
Theo Steininger's avatar
Theo Steininger committed
149

150
        if self.unitary:
151
            return self.inverse_times(x)
152

153
        self._check_input_compatibility(x, inverse=True)
154
        try:
155
            y = self._adjoint_times(x)
156
157
        except(NotImplementedError):
            if (self.unitary):
158
                y = self._inverse_times(x)
159
160
            else:
                raise
161
162
        return y

163
    def adjoint_inverse_times(self, x):
164
165
166
167
168
169
        """ 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
170
        x : Field
171
172
173
174
            applies the Operator to the given Field

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
175
176
        out : Field
            The processed Field living on the target-domain.
177

Theo Steininger's avatar
Theo Steininger committed
178
179
180
181
        Notes
        -----
        If the operator has an `inverse` then the inverse adjoint is identical
        to the adjoint inverse. We provide both names for convenience.
182
        """
183
        self._check_input_compatibility(x)
184
        try:
185
            y = self._adjoint_inverse_times(x)
186
187
        except(NotImplementedError):
            if self.unitary:
188
                y = self._times(x)
189
190
            else:
                raise
191
192
        return y

193
194
    def inverse_adjoint_times(self, x):
        return self.adjoint_inverse_times(x)
195

196
    def _times(self, x):
197
198
        raise NotImplementedError(
            "no generic instance method 'times'.")
199

200
    def _adjoint_times(self, x):
201
202
        raise NotImplementedError(
            "no generic instance method 'adjoint_times'.")
203

204
    def _inverse_times(self, x):
205
206
        raise NotImplementedError(
            "no generic instance method 'inverse_times'.")
207

208
    def _adjoint_inverse_times(self, x):
209
210
        raise NotImplementedError(
            "no generic instance method 'adjoint_inverse_times'.")
211

212
    def _check_input_compatibility(self, x, inverse=False):
213
        if not isinstance(x, Field):
Martin Reinecke's avatar
updates    
Martin Reinecke committed
214
            raise ValueError("supplied object is not a `Field`.")
215

216
217
218
        if x.domain != (self.target if inverse else self.domain):
            raise ValueError("The operator's and and field's domains "
                             "don't match.")
219
220
221

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