linear_operator.py 5.99 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
from ..utilities import NiftyMeta
Martin Reinecke's avatar
Martin Reinecke committed
22
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
    ----------
Martin Reinecke's avatar
Martin Reinecke committed
36
    domain : DomainTuple
Theo Steininger's avatar
Theo Steininger committed
37
        The domain on which the Operator's input Field lives.
Martin Reinecke's avatar
Martin Reinecke committed
38
    target : DomainTuple
Theo Steininger's avatar
Theo Steininger committed
39
        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
    @property
68
    def unitary(self):
69
70
71
        """
        unitary : boolean
            States whether the Operator is unitary or not.
72
73
            Since the majority of operators will not be unitary, this property
            returns False, unless it is overridden in a subclass.
74
        """
75
        return False
76

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
    def inverse_adjoint_times(self, x):
Martin Reinecke's avatar
Martin Reinecke committed
174
        """Same as adjoint_inverse_times()"""
175
        return self.adjoint_inverse_times(x)
176

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

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

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

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

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

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

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