linear_operator.py 6.03 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
from ..nifty_meta import NiftyMeta
from ..field import Field
Martin Reinecke's avatar
Martin Reinecke committed
23
from future.utils import with_metaclass
24
25


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

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


    Attributes
    ----------
Theo Steininger's avatar
Theo Steininger committed
36
37
38
39
    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.
40
    unitary : boolean
Theo Steininger's avatar
Theo Steininger committed
41
        Indicates whether the Operator is unitary or not.
42
43
    """

44
45
    def __init__(self):
        pass
46

47
    @abc.abstractproperty
48
    def domain(self):
49
        """
Martin Reinecke's avatar
Martin Reinecke committed
50
        domain : DomainTuple
Theo Steininger's avatar
Theo Steininger committed
51
52
            The domain on which the Operator's input Field lives.
            Every Operator which inherits from the abstract LinearOperator
53
54
            base class must have this attribute.
        """
55
        raise NotImplementedError
56
57
58

    @abc.abstractproperty
    def target(self):
59
        """
Martin Reinecke's avatar
Martin Reinecke committed
60
        target : DomainTuple
Theo Steininger's avatar
Theo Steininger committed
61
62
            The domain on which the Operator's output Field lives.
            Every Operator which inherits from the abstract LinearOperator
63
64
            base class must have this attribute.
        """
65
66
        raise NotImplementedError

67
68
    @abc.abstractproperty
    def unitary(self):
69
70
71
72
73
74
        """
        unitary : boolean
            States whether the Operator is unitary or not.
            Every Operator which inherits from the abstract LinearOperator
            base class must have this attribute.
        """
75
76
        raise NotImplementedError

77
78
    def __call__(self, x):
        return self.times(x)
79

80
    def times(self, x):
81
82
83
84
        """ Applies the Operator to a given Field.

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
85
        x : Field
Martin Reinecke's avatar
Martin Reinecke committed
86
            The input Field, living on the Operator's domain.
87
88
89

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
90
        out : Field
Martin Reinecke's avatar
Martin Reinecke committed
91
            The processed Field living on the Operator's target domain.
92
        """
93
94
        self._check_input_compatibility(x)
        return self._times(x)
95

96
    def inverse_times(self, x):
Martin Reinecke's avatar
Martin Reinecke committed
97
        """Applies the inverse Operator to a given Field.
98
99
100

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
101
        x : Field
Martin Reinecke's avatar
Martin Reinecke committed
102
            The input Field, living on the Operator's target domain
103
104
105

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
106
        out : Field
Martin Reinecke's avatar
Martin Reinecke committed
107
            The processed Field living on the Operator's domain.
108
        """
109
        self._check_input_compatibility(x, inverse=True)
110
        try:
111
            y = self._inverse_times(x)
112
113
        except NotImplementedError:
            if self.unitary:
114
                y = self._adjoint_times(x)
115
116
            else:
                raise
117
118
        return y

119
    def adjoint_times(self, x):
Martin Reinecke's avatar
Martin Reinecke committed
120
        """Applies the adjoint-Operator to a given Field.
121
122
123

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
124
        x : Field
Martin Reinecke's avatar
Martin Reinecke committed
125
            The input Field, living on the Operator's target domain
126
127
128

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
129
        out : Field
Martin Reinecke's avatar
Martin Reinecke committed
130
            The processed Field living on the Operator's domain.
131
        """
132
        if self.unitary:
133
            return self.inverse_times(x)
134

135
        self._check_input_compatibility(x, inverse=True)
136
        try:
137
            y = self._adjoint_times(x)
138
139
        except NotImplementedError:
            if self.unitary:
140
                y = self._inverse_times(x)
141
142
            else:
                raise
143
144
        return y

145
    def adjoint_inverse_times(self, x):
146
147
148
149
        """ Applies the adjoint-inverse Operator to a given Field.

        Parameters
        ----------
Theo Steininger's avatar
Theo Steininger committed
150
        x : Field
Martin Reinecke's avatar
Martin Reinecke committed
151
            The input Field, living on the Operator's domain.
152
153
154

        Returns
        -------
Theo Steininger's avatar
Theo Steininger committed
155
        out : Field
Martin Reinecke's avatar
Martin Reinecke committed
156
            The processed Field living on the Operator's target domain.
157

Theo Steininger's avatar
Theo Steininger committed
158
159
160
161
        Notes
        -----
        If the operator has an `inverse` then the inverse adjoint is identical
        to the adjoint inverse. We provide both names for convenience.
162
        """
163
        self._check_input_compatibility(x)
164
        try:
165
            y = self._adjoint_inverse_times(x)
166
        except NotImplementedError:
167
            if self.unitary:
168
                y = self._times(x)
169
170
            else:
                raise
171
172
        return y

173
174
    def inverse_adjoint_times(self, x):
        return self.adjoint_inverse_times(x)
175

176
    def _times(self, x):
177
178
        raise NotImplementedError(
            "no generic instance method 'times'.")
179

180
    def _adjoint_times(self, x):
181
182
        raise NotImplementedError(
            "no generic instance method 'adjoint_times'.")
183

184
    def _inverse_times(self, x):
185
186
        raise NotImplementedError(
            "no generic instance method 'inverse_times'.")
187

188
    def _adjoint_inverse_times(self, x):
189
190
        raise NotImplementedError(
            "no generic instance method 'adjoint_inverse_times'.")
191

192
    def _check_input_compatibility(self, x, inverse=False):
193
        if not isinstance(x, Field):
Martin Reinecke's avatar
updates    
Martin Reinecke committed
194
            raise ValueError("supplied object is not a `Field`.")
195

196
197
198
        if x.domain != (self.target if inverse else self.domain):
            raise ValueError("The operator's and and field's domains "
                             "don't match.")
199
200
201

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