composed_operator.py 5.11 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 range
Martin Reinecke's avatar
Martin Reinecke committed
20
from ..linear_operator import LinearOperator
Martin Reinecke's avatar
Martin Reinecke committed
21
from ... import DomainTuple
22
23

class ComposedOperator(LinearOperator):
Theo Steininger's avatar
Theo Steininger committed
24
    """ NIFTY class for composed operators.
25

Theo Steininger's avatar
Theo Steininger committed
26
    The  NIFTY composed operator class combines multiple linear operators.
27
28
29

    Parameters
    ----------
Theo Steininger's avatar
Theo Steininger committed
30
31
    operators : tuple of NIFTy Operators
        The tuple of LinearOperators.
32
33
34
35
    default_spaces : tuple of ints *optional*
        Defines on which space(s) of a given field the Operator acts by
        default (default: None)

36
37
38

    Attributes
    ----------
Theo Steininger's avatar
Theo Steininger committed
39
    domain : tuple of DomainObjects, i.e. Spaces and FieldTypes
40
        The NIFTy.space in which the operator is defined.
Theo Steininger's avatar
Theo Steininger committed
41
    target : tuple of DomainObjects, i.e. Spaces and FieldTypes
42
        The NIFTy.space in which the outcome of the operator lives
43
44
    unitary : boolean
        Indicates whether the Operator is unitary or not.
45
46
47
48
49

    Raises
    ------
    TypeError
        Raised if
Theo Steininger's avatar
Theo Steininger committed
50
51
            * an element of the operator list is not an instance of the
              LinearOperator-baseclass.
52
53
54

    Notes
    -----
Martin Reinecke's avatar
Martin Reinecke committed
55
    Very useful in case one has to transform a Field living over a product
Theo Steininger's avatar
Theo Steininger committed
56
    space (see example below).
57
58
59

    Examples
    --------
Theo Steininger's avatar
Theo Steininger committed
60
61
    Minimal example of transforming a Field living on two domains into its
    harmonic space.
62
63
64
65
66

    >>> x1 = RGSpace(5)
    >>> x2 = RGSpace(10)
    >>> k1 = RGRGTransformation.get_codomain(x1)
    >>> k2 = RGRGTransformation.get_codomain(x2)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
67
68
    >>> FFT1 = FFTOperator(domain=x1, target=k1)
    >>> FFT2 = FFTOperator(domain=x2, target=k2)
69
70
71
72
73
74
    >>> FFT = ComposedOperator((FFT1, FFT2)
    >>> f = Field.from_random('normal', domain=(x1,x2))
    >>> FFT.times(f)

    """

75
    # ---Overwritten properties and methods---
76
77
78
    def __init__(self, operators, default_spaces=None):
        super(ComposedOperator, self).__init__(default_spaces)

79
80
81
82
83
84
85
        self._operator_store = ()
        for op in operators:
            if not isinstance(op, LinearOperator):
                raise TypeError("The elements of the operator list must be"
                                "instances of the LinearOperator-baseclass")
            self._operator_store += (op,)

86
    def _check_input_compatibility(self, x, spaces, inverse=False):
87
88
89
90
91
        """
        The input check must be disabled for the ComposedOperator, since it
        is not easily forecasteable what the output of an operator-call
        will look like.
        """
92
93
        if spaces is None:
            spaces = self.default_spaces
94
        return spaces
95
96
97
98
99

    # ---Mandatory properties and methods---
    @property
    def domain(self):
        if not hasattr(self, '_domain'):
Martin Reinecke's avatar
Martin Reinecke committed
100
            dom = ()
101
            for op in self._operator_store:
Martin Reinecke's avatar
Martin Reinecke committed
102
103
                dom += op.domain.domains
            self._domain = DomainTuple.make(dom)
104
105
106
107
108
        return self._domain

    @property
    def target(self):
        if not hasattr(self, '_target'):
Martin Reinecke's avatar
Martin Reinecke committed
109
            tgt = ()
110
            for op in self._operator_store:
Martin Reinecke's avatar
Martin Reinecke committed
111
112
                tgt += op.target.domains
            self._target = DomainTuple.make(tgt)
113
114
115
116
117
118
        return self._target

    @property
    def unitary(self):
        return False

119
120
    def _times(self, x, spaces):
        return self._times_helper(x, spaces, func='times')
121

122
123
    def _adjoint_times(self, x, spaces):
        return self._inverse_times_helper(x, spaces, func='adjoint_times')
124

125
126
    def _inverse_times(self, x, spaces):
        return self._inverse_times_helper(x, spaces, func='inverse_times')
127

128
129
    def _adjoint_inverse_times(self, x, spaces):
        return self._times_helper(x, spaces, func='adjoint_inverse_times')
130

131
    def _times_helper(self, x, spaces, func):
132
133
        space_index = 0
        if spaces is None:
Martin Reinecke's avatar
Martin Reinecke committed
134
            spaces = range(len(self.domain))
135
136
137
138
        for op in self._operator_store:
            active_spaces = spaces[space_index:space_index+len(op.domain)]
            space_index += len(op.domain)

139
            x = getattr(op, func)(x, spaces=active_spaces)
140
141
        return x

142
    def _inverse_times_helper(self, x, spaces, func):
143
144
        space_index = 0
        if spaces is None:
Martin Reinecke's avatar
Martin Reinecke committed
145
            spaces = range(len(self.target))
146
        rev_spaces = spaces[::-1]
147
        for op in reversed(self._operator_store):
148
            active_spaces = rev_spaces[space_index:space_index+len(op.target)]
149
150
            space_index += len(op.target)

151
            x = getattr(op, func)(x, spaces=active_spaces[::-1])
152
        return x