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
Martin Reinecke's avatar
Martin Reinecke committed
21
22
23
from ..nifty_meta import NiftyMeta
from ..field import Field
from .. import nifty_utilities as utilities
Martin Reinecke's avatar
Martin Reinecke committed
24
from future.utils import with_metaclass
25
26


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

31
    The base NIFTY operator class is an abstract class from which
32
    other specific operator subclasses are derived.
33
34
35
36


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

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

53
54
    def __init__(self):
        pass
55

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

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

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

86
87
    def __call__(self, x):
        return self.times(x)
88

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

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

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

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

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

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

148
        if self.unitary:
149
            return self.inverse_times(x)
150

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

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

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

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

191
192
    def inverse_adjoint_times(self, x):
        return self.adjoint_inverse_times(x)
193

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

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

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

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

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

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

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