Source code for nifty8.operators.linear_operator

# 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/>.
#
# Copyright(C) 2013-2020 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.

from ..utilities import check_object_identity
from .operator import Operator


[docs] class LinearOperator(Operator): """NIFTY base class for linear operators. The base NIFTY operator class is an abstract class from which other specific operator subclasses are derived. Attributes ---------- TIMES : int Symbolic constant representing normal operator application ADJOINT_TIMES : int Symbolic constant representing adjoint operator application INVERSE_TIMES : int Symbolic constant representing inverse operator application ADJOINT_INVERSE_TIMES : int Symbolic constant representing adjoint inverse operator application INVERSE_ADJOINT_TIMES : int same as ADJOINT_INVERSE_TIMES Notes ----- The symbolic constants for the operation modes can be combined by the "bitwise-or" operator "|", for expressing the capability of the operator by means of a single integer number. """ # Field Operator Modes TIMES = 1 ADJOINT_TIMES = 2 INVERSE_TIMES = 4 ADJOINT_INVERSE_TIMES = 8 INVERSE_ADJOINT_TIMES = 8 # Operator Transform Flags ADJOINT_BIT = 1 INVERSE_BIT = 2 _ilog = (-1, 0, 1, -1, 2, -1, -1, -1, 3) _validMode = (False, True, True, False, True, False, False, False, True) _modeTable = ((1, 2, 4, 8), (2, 1, 8, 4), (4, 8, 1, 2), (8, 4, 2, 1)) _capTable = ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), (0, 2, 1, 3, 8, 10, 9, 11, 4, 6, 5, 7, 12, 14, 13, 15), (0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15), (0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15)) _addInverse = (0, 5, 10, 15, 5, 5, 15, 15, 10, 15, 10, 15, 15, 15, 15, 15) _backwards = 6 _all_ops = 15 def _dom(self, mode): return self.domain if (mode & 9) else self.target def _tgt(self, mode): return self.domain if (mode & 6) else self.target def _flip_modes(self, trafo): from .operator_adapter import OperatorAdapter return self if trafo == 0 else OperatorAdapter(self, trafo) @property def inverse(self): """LinearOperator : the inverse of `self` Returns a LinearOperator object which behaves as if it were the inverse of this operator.""" return self._flip_modes(self.INVERSE_BIT) @property def adjoint(self): """LinearOperator : the adjoint of `self` Returns a LinearOperator object which behaves as if it were the adjoint of this operator.""" return self._flip_modes(self.ADJOINT_BIT) def __matmul__(self, other): if isinstance(other, LinearOperator): from .chain_operator import ChainOperator return ChainOperator.make([self, other]) return Operator.__matmul__(self, other) def __rmatmul__(self, other): if isinstance(other, LinearOperator): from .chain_operator import ChainOperator return ChainOperator.make([other, self]) return Operator.__rmatmul__(self, other) def _myadd(self, other, oneg): from .sum_operator import SumOperator return SumOperator.make((self, other), (False, oneg)) def __add__(self, other): if isinstance(other, LinearOperator): return self._myadd(other, False) return Operator.__add__(self, other) def __radd__(self, other): return self.__add__(other) def __sub__(self, other): if isinstance(other, LinearOperator): return self._myadd(other, True) return Operator.__sub__(self, other) def __rsub__(self, other): if isinstance(other, LinearOperator): return other._myadd(self, True) return Operator.__rsub__(self, other) @property def capability(self): """int : the supported operation modes Returns the supported subset of :attr:`TIMES`, :attr:`ADJOINT_TIMES`, :attr:`INVERSE_TIMES`, and :attr:`ADJOINT_INVERSE_TIMES`, joined together by the "|" operator. """ return self._capability
[docs] def force(self, x): return self.apply(x.extract(self.domain), self.TIMES)
[docs] def apply(self, x, mode): """Applies the Operator to a given `x`, in a specified `mode`. Parameters ---------- x : :class:`nifty8.field.Field` The input Field, defined on the Operator's domain or target, depending on mode. mode : int - :attr:`TIMES`: normal application - :attr:`ADJOINT_TIMES`: adjoint application - :attr:`INVERSE_TIMES`: inverse application - :attr:`ADJOINT_INVERSE_TIMES` or :attr:`INVERSE_ADJOINT_TIMES`: adjoint inverse application Returns ------- :class:`nifty8.field.Field` The processed Field defined on the Operator's target or domain, depending on mode. """ raise NotImplementedError
[docs] def __call__(self, x): """Same as :meth:`times`""" if x.jac is not None: return x.new(self(x._val), self).prepend_jac(x.jac) if x.val is not None: return self.apply(x, self.TIMES) return self@x
[docs] def times(self, x): """Applies the Operator to a given Field. Parameters ---------- x : :class:`nifty8.field.Field` The input Field, defined on the Operator's domain. Returns ------- :class:`nifty8.field.Field` The processed Field defined on the Operator's target domain. """ return self.apply(x, self.TIMES)
[docs] def inverse_times(self, x): """Applies the inverse Operator to a given Field. Parameters ---------- x : :class:`nifty8.field.Field` The input Field, defined on the Operator's target domain Returns ------- :class:`nifty8.field.Field` The processed Field defined on the Operator's domain. """ return self.apply(x, self.INVERSE_TIMES)
[docs] def adjoint_times(self, x): """Applies the adjoint-Operator to a given Field. Parameters ---------- x : :class:`nifty8.field.Field` The input Field, defined on the Operator's target domain Returns ------- :class:`nifty8.field.Field` The processed Field defined on the Operator's domain. """ return self.apply(x, self.ADJOINT_TIMES)
[docs] def adjoint_inverse_times(self, x): """Applies the adjoint-inverse Operator to a given Field. Parameters ---------- x : :class:`nifty8.field.Field` The input Field, defined on the Operator's domain. Returns ------- :class:`nifty8.field.Field` The processed Field defined on the Operator's target domain. Notes ----- If the operator has an `inverse` then the inverse adjoint is identical to the adjoint inverse. We provide both names for convenience. """ return self.apply(x, self.ADJOINT_INVERSE_TIMES)
[docs] def inverse_adjoint_times(self, x): """Same as :meth:`adjoint_inverse_times`""" return self.apply(x, self.ADJOINT_INVERSE_TIMES)
def _check_mode(self, mode): if not self._validMode[mode]: raise NotImplementedError("invalid operator mode specified") if mode & self.capability == 0: raise NotImplementedError( "requested operator mode is not supported") def _check_input(self, x, mode): self._check_mode(mode) check_object_identity(self._dom(mode), x.domain)