Commit af9bdeb9 authored by Martin Reinecke's avatar Martin Reinecke
Browse files

remove ComposedOperator

parent 32a08573
Pipeline #23263 failed with stage
in 4 minutes and 1 second
...@@ -34,7 +34,7 @@ if __name__ == "__main__": ...@@ -34,7 +34,7 @@ if __name__ == "__main__":
# Instrument._diagonal.val[64:512-64, 64:512-64] = 0 # Instrument._diagonal.val[64:512-64, 64:512-64] = 0
# Add a harmonic transformation to the instrument # Add a harmonic transformation to the instrument
R = ift.ComposedOperator([fft, Instrument]) R = Instrument*fft
noise = 1. noise = 1.
N = ift.DiagonalOperator(ift.Field.full(s_space, noise).weight(1)) N = ift.DiagonalOperator(ift.Field.full(s_space, noise).weight(1))
......
...@@ -36,7 +36,7 @@ if __name__ == "__main__": ...@@ -36,7 +36,7 @@ if __name__ == "__main__":
R = ift.ResponseOperator(signal_space, sigma=(response_sigma,), R = ift.ResponseOperator(signal_space, sigma=(response_sigma,),
exposure=(mask,)) exposure=(mask,))
data_domain = R.target[0] data_domain = R.target[0]
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = R*fft
# Setting up the noise covariance and drawing a random noise realization # Setting up the noise covariance and drawing a random noise realization
ndiag = ift.Field.full(data_domain, mock_signal.var()/signal_to_noise) ndiag = ift.Field.full(data_domain, mock_signal.var()/signal_to_noise)
...@@ -66,20 +66,16 @@ if __name__ == "__main__": ...@@ -66,20 +66,16 @@ if __name__ == "__main__":
# m3 = fft(me3[0].position) # m3 = fft(me3[0].position)
# Plotting # Plotting
ift.plotting.plot(mock_signal, name='mock_signal.png', colormap="plasma", plotdict = {"xlabel": "Pixel index", "ylabel": "Pixel index",
xlabel="Pixel Index", ylabel="Pixel Index") "colormap": "Planck-like"}
ift.plotting.plot(mock_signal, name="mock_signal.png", **plotdict)
logdata = np.log(ift.dobj.to_global_data(data.val)).reshape(signal_space.shape) logdata = np.log(ift.dobj.to_global_data(data.val)).reshape(signal_space.shape)
ift.plotting.plot(ift.Field(signal_space, ift.plotting.plot(ift.Field(signal_space,
val=ift.dobj.from_global_data(logdata)), val=ift.dobj.from_global_data(logdata)),
name="log_of_data.png", colormap="plasma", name="log_of_data.png", **plotdict)
xlabel="Pixel Index", ylabel="Pixel Index") # ift.plotting.plot(m1,name='m_LBFGS.png', **plotdict)
# ift.plotting.plot(m1,name='m_LBFGS.png', colormap="plasma", ift.plotting.plot(m2, name='m_Newton.png', **plotdict)
# xlabel="Pixel Index", ylabel="Pixel Index") # ift.plotting.plot(m3, name='m_SteepestDescent.png', **plotdict)
ift.plotting.plot(m2, name='m_Newton.png', colormap="plasma",
xlabel="Pixel Index", ylabel="Pixel Index")
# ift.plotting.plot(m3, name='m_SteepestDescent.png',
# colormap="plasma", xlabel="Pixel Index",
# ylabel="Pixel Index")
# Probing the variance # Probing the variance
class Proby(ift.DiagonalProberMixin, ift.Prober): class Proby(ift.DiagonalProberMixin, ift.Prober):
...@@ -89,4 +85,4 @@ if __name__ == "__main__": ...@@ -89,4 +85,4 @@ if __name__ == "__main__":
sm = ift.FFTSmoothingOperator(signal_space, sigma=0.02) sm = ift.FFTSmoothingOperator(signal_space, sigma=0.02)
variance = sm(proby.diagonal.weight(-1)) variance = sm(proby.diagonal.weight(-1))
ift.plotting.plot(variance, name='variance.png') ift.plotting.plot(variance, name='variance.png', **plotdict)
...@@ -50,8 +50,8 @@ if __name__ == "__main__": ...@@ -50,8 +50,8 @@ if __name__ == "__main__":
MaskOperator = ift.DiagonalOperator(mask) MaskOperator = ift.DiagonalOperator(mask)
InstrumentResponse = ift.ResponseOperator(s_space, sigma=[0.0]) InstrumentResponse = ift.ResponseOperator(s_space, sigma=[0.0])
MeasurementOperator = ift.ComposedOperator([MaskOperator, MeasurementOperator = InstrumentResponse*MaskOperator
InstrumentResponse])
d_space = MeasurementOperator.target d_space = MeasurementOperator.target
noise_covariance = ift.Field(d_space, val=noise_level**2).weight() noise_covariance = ift.Field(d_space, val=noise_level**2).weight()
......
...@@ -44,7 +44,7 @@ if __name__ == "__main__": ...@@ -44,7 +44,7 @@ if __name__ == "__main__":
mock_power_2 = ift.PS_field(power_space_2, power_spectrum_2) mock_power_2 = ift.PS_field(power_space_2, power_spectrum_2)
fft = ift.ComposedOperator((fft_1, fft_2)) fft = fft_2*fft_1
mock_power = ift.Field( mock_power = ift.Field(
(power_space_1, power_space_2), (power_space_1, power_space_2),
...@@ -74,7 +74,7 @@ if __name__ == "__main__": ...@@ -74,7 +74,7 @@ if __name__ == "__main__":
sigma=(response_sigma_1, response_sigma_2), sigma=(response_sigma_1, response_sigma_2),
exposure=(mask_1, mask_2)) exposure=(mask_1, mask_2))
data_domain = R.target data_domain = R.target
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = R*fft
# Setting up the noise covariance and drawing a random noise realization # Setting up the noise covariance and drawing a random noise realization
ndiag = ift.Field.full(data_domain, mock_signal.var()/signal_to_noise) ndiag = ift.Field.full(data_domain, mock_signal.var()/signal_to_noise)
...@@ -105,13 +105,15 @@ if __name__ == "__main__": ...@@ -105,13 +105,15 @@ if __name__ == "__main__":
plot_space = ift.RGSpace((N_pixels_1, N_pixels_2)) plot_space = ift.RGSpace((N_pixels_1, N_pixels_2))
sm = ift.FFTSmoothingOperator(plot_space, sigma=0.03) sm = ift.FFTSmoothingOperator(plot_space, sigma=0.03)
plotdict = {"xlabel": "Pixel index", "ylabel": "Pixel index",
"colormap": "Planck-like"}
ift.plotting.plot( ift.plotting.plot(
ift.log(ift.sqrt(sm(ift.Field(plot_space, val=variance.val.real)))), ift.log(ift.sqrt(sm(ift.Field(plot_space, val=variance.val.real)))),
name='uncertainty.png', zmin=0., zmax=3., title="Uncertainty map", name='uncertainty.png', zmin=0., zmax=3., title="Uncertainty map",
colormap="Planck-like") **plotdict)
ift.plotting.plot(ift.Field(plot_space, val=mock_signal.val.real), ift.plotting.plot(ift.Field(plot_space, val=mock_signal.val.real),
name='mock_signal.png', colormap="Planck-like") name='mock_signal.png', **plotdict)
ift.plotting.plot(ift.Field(plot_space, val=data.val.real), ift.plotting.plot(ift.Field(plot_space, val=data.val.real),
name='data.png', colormap="Planck-like") name='data.png', **plotdict)
ift.plotting.plot(ift.Field(plot_space, val=m.val.real), ift.plotting.plot(ift.Field(plot_space, val=m.val.real),
name='map.png', colormap="Planck-like") name='map.png', **plotdict)
...@@ -38,7 +38,7 @@ if __name__ == "__main__": ...@@ -38,7 +38,7 @@ if __name__ == "__main__":
R = ift.ResponseOperator(signal_space, sigma=(response_sigma,), R = ift.ResponseOperator(signal_space, sigma=(response_sigma,),
exposure=(mask,)) exposure=(mask,))
data_domain = R.target[0] data_domain = R.target[0]
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = R * fft
# Setting up the noise covariance and drawing a random noise realization # Setting up the noise covariance and drawing a random noise realization
ndiag = ift.Field.full(data_domain, mock_signal.var()/signal_to_noise) ndiag = ift.Field.full(data_domain, mock_signal.var()/signal_to_noise)
...@@ -67,12 +67,10 @@ if __name__ == "__main__": ...@@ -67,12 +67,10 @@ if __name__ == "__main__":
variance = ift.sqrt(sm(proby.diagonal.weight(-1))) variance = ift.sqrt(sm(proby.diagonal.weight(-1)))
# Plotting # Plotting
ift.plotting.plot(variance, name="uncertainty.png", xlabel='Pixel index', plotdict = {"xlabel": "Pixel index", "ylabel": "Pixel index",
ylabel='Pixel index') "colormap": "Planck-like"}
ift.plotting.plot(mock_signal, name="mock_signal.png", ift.plotting.plot(variance, name="uncertainty.png", **plotdict)
xlabel='Pixel index', ylabel='Pixel index') ift.plotting.plot(mock_signal, name="mock_signal.png", **plotdict)
ift.plotting.plot(ift.Field(signal_space, val=data.val), ift.plotting.plot(ift.Field(signal_space, val=data.val),
name="data.png", xlabel='Pixel index', name="data.png", **plotdict)
ylabel='Pixel index') ift.plotting.plot(m, name="map.png", **plotdict)
ift.plotting.plot(m, name="map.png", xlabel='Pixel index',
ylabel='Pixel index')
...@@ -53,7 +53,7 @@ if __name__ == "__main__": ...@@ -53,7 +53,7 @@ if __name__ == "__main__":
R = ift.ResponseOperator(signal_space, sigma=(response_sigma,), R = ift.ResponseOperator(signal_space, sigma=(response_sigma,),
exposure=(exposure,)) exposure=(exposure,))
data_domain = R.target[0] data_domain = R.target[0]
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = R*fft
N = ift.DiagonalOperator( N = ift.DiagonalOperator(
ift.Field.full(data_domain, ift.Field.full(data_domain,
......
...@@ -18,7 +18,7 @@ if __name__ == "__main__": ...@@ -18,7 +18,7 @@ if __name__ == "__main__":
# Choosing the prior correlation structure and defining # Choosing the prior correlation structure and defining
# correlation operator # correlation operator
p_spec = (lambda k: (42 / (k + 1) ** 3)) p_spec = (lambda k: (42/(k+1)**3))
S = ift.create_power_operator(h_space, power_spectrum=p_spec) S = ift.create_power_operator(h_space, power_spectrum=p_spec)
...@@ -35,7 +35,7 @@ if __name__ == "__main__": ...@@ -35,7 +35,7 @@ if __name__ == "__main__":
Instrument = ift.DiagonalOperator(diag) Instrument = ift.DiagonalOperator(diag)
# Adding a harmonic transformation to the instrument # Adding a harmonic transformation to the instrument
R = ift.ComposedOperator([fft, Instrument]) R = Instrument*fft
signal_to_noise = 1. signal_to_noise = 1.
ndiag = ift.Field.full(s_space, ss.var()/signal_to_noise) ndiag = ift.Field.full(s_space, ss.var()/signal_to_noise)
N = ift.DiagonalOperator(ndiag.weight(1)) N = ift.DiagonalOperator(ndiag.weight(1))
......
...@@ -31,7 +31,7 @@ class WienerFilterCurvature(EndomorphicOperator): ...@@ -31,7 +31,7 @@ class WienerFilterCurvature(EndomorphicOperator):
@property @property
def domain(self): def domain(self):
return self.S.domain return self._op.domain
@property @property
def capability(self): def capability(self):
......
...@@ -6,7 +6,6 @@ from .fft_smoothing_operator import FFTSmoothingOperator ...@@ -6,7 +6,6 @@ from .fft_smoothing_operator import FFTSmoothingOperator
from .direct_smoothing_operator import DirectSmoothingOperator from .direct_smoothing_operator import DirectSmoothingOperator
from .fft_operator import FFTOperator from .fft_operator import FFTOperator
from .inversion_enabler import InversionEnabler from .inversion_enabler import InversionEnabler
from .composed_operator import ComposedOperator
from .response_operator import ResponseOperator from .response_operator import ResponseOperator
from .laplace_operator import LaplaceOperator from .laplace_operator import LaplaceOperator
from .smoothness_operator import SmoothnessOperator from .smoothness_operator import SmoothnessOperator
......
# 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-2017 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
from .linear_operator import LinearOperator
class ComposedOperator(LinearOperator):
""" NIFTY class for composed operators.
The NIFTY composed operator class combines multiple linear operators.
Parameters
----------
operators : tuple of NIFTy Operators
The tuple of LinearOperators.
Attributes
----------
domain : DomainTuple
The NIFTy.space in which the operator is defined.
target : DomainTuple
The NIFTy.space in which the outcome of the operator lives
"""
def __init__(self, operators):
super(ComposedOperator, self).__init__()
self._operator_store = ()
old_op = None
self._capability = operators[0].capability
for op in operators:
if not isinstance(op, LinearOperator):
raise TypeError("The elements of the operator list must be"
"instances of the LinearOperator base class")
if old_op is not None and op.domain != old_op.target:
raise ValueError("incompatible domains")
self._operator_store += (op,)
self._capability &= op.capability
old_op = op
if self._capability == 0:
raise ValueError("composed operator does not support any mode")
@property
def domain(self):
return self._operator_store[0].domain
@property
def target(self):
return self._operator_store[-1].target
@property
def capability(self):
return self._capability
def apply(self, x, mode):
self._check_mode(mode)
if mode == self.TIMES or mode == self.ADJOINT_INVERSE_TIMES:
for op in self._operator_store:
x = op.apply(x, mode)
else:
for op in reversed(self._operator_store):
x = op.apply(x, mode)
return x
...@@ -54,20 +54,6 @@ class FFTOperator(LinearOperator): ...@@ -54,20 +54,6 @@ class FFTOperator(LinearOperator):
For GLSpace, HPSpace, and LMSpace, a sensible (but not unique) For GLSpace, HPSpace, and LMSpace, a sensible (but not unique)
co-domain is chosen that should work satisfactorily in most situations, co-domain is chosen that should work satisfactorily in most situations,
but for full control, the user should explicitly specify a codomain. but for full control, the user should explicitly specify a codomain.
Attributes
----------
domain: Tuple of Spaces
The domain of the data that is input by "times" and output by
"adjoint_times".
target: Tuple of Spaces
The domain of the data that is output by "times" and input by
"adjoint_times".
Raises
------
ValueError:
if "domain" or "target" are not of the proper type.
""" """
def __init__(self, domain, target=None, space=None): def __init__(self, domain, target=None, space=None):
...@@ -96,7 +82,8 @@ class FFTOperator(LinearOperator): ...@@ -96,7 +82,8 @@ class FFTOperator(LinearOperator):
else: else:
self._trafo = SphericalTransformation(pdom, hdom, self._space) self._trafo = SphericalTransformation(pdom, hdom, self._space)
def _times_helper(self, x): def apply(self, x, mode):
self._check_input(x, mode)
if np.issubdtype(x.dtype, np.complexfloating): if np.issubdtype(x.dtype, np.complexfloating):
res = (self._trafo.transform(x.real) + res = (self._trafo.transform(x.real) +
1j * self._trafo.transform(x.imag)) 1j * self._trafo.transform(x.imag))
...@@ -104,10 +91,6 @@ class FFTOperator(LinearOperator): ...@@ -104,10 +91,6 @@ class FFTOperator(LinearOperator):
res = self._trafo.transform(x) res = self._trafo.transform(x)
return res return res
def apply(self, x, mode):
self._check_input(x, mode)
return self._times_helper(x)
@property @property
def domain(self): def domain(self):
return self._domain return self._domain
......
...@@ -2,8 +2,9 @@ from builtins import range ...@@ -2,8 +2,9 @@ from builtins import range
from .. import Field, FieldArray, DomainTuple from .. import Field, FieldArray, DomainTuple
from .linear_operator import LinearOperator from .linear_operator import LinearOperator
from .fft_smoothing_operator import FFTSmoothingOperator from .fft_smoothing_operator import FFTSmoothingOperator
from .composed_operator import ComposedOperator
from .diagonal_operator import DiagonalOperator from .diagonal_operator import DiagonalOperator
from .scaling_operator import ScalingOperator
import numpy as np
class ResponseOperator(LinearOperator): class ResponseOperator(LinearOperator):
...@@ -23,47 +24,39 @@ class ResponseOperator(LinearOperator): ...@@ -23,47 +24,39 @@ class ResponseOperator(LinearOperator):
Defines the smoothing length of the operator for each space it lives on Defines the smoothing length of the operator for each space it lives on
exposure : list(np.float) exposure : list(np.float)
Defines the exposure of the operator for each space it lives on Defines the exposure of the operator for each space it lives on
default_spaces : tuple of ints *optional* spaces : tuple of ints *optional*
Defines on which space(s) of a given field the Operator acts by Defines on which subdomain the individual components act
default (default: None) (default: None)
Attributes
----------
domain : DomainTuple
The domain on which the Operator's input Field lives.
target : DomainTuple
The domain in which the outcome of the operator lives.
Raises
------
ValueError:
raised if:
* len of sigma-list and exposure-list are not equal
""" """
def __init__(self, domain, sigma=[1.], exposure=[1.], spaces=None): def __init__(self, domain, sigma, exposure, spaces=None):
super(ResponseOperator, self).__init__() super(ResponseOperator, self).__init__()
if len(sigma) != len(exposure):
raise ValueError("Length of smoothing kernel and length of"
"exposure do not match")
nsigma = len(sigma)
self._domain = DomainTuple.make(domain) self._domain = DomainTuple.make(domain)
if spaces is None: if spaces is None:
spaces = range(len(self._domain)) spaces = tuple(range(len(self._domain)))
if len(sigma) != len(exposure) or len(sigma) != len(spaces):
kernel_smoothing = [ raise ValueError("length mismatch between sigma, exposure, "
FFTSmoothingOperator(self._domain, sigma[x], space=spaces[x]) "and spaces ")
for x in range(nsigma)] ncomp = len(sigma)
self._composed_kernel = ComposedOperator(kernel_smoothing) if ncomp == 0:
raise ValueError("Empty response operator not allowed")
kernel_exposure = [
DiagonalOperator(Field(self._domain[spaces[x]], exposure[x]), self._kernel = None
domain=self._domain, spaces=(spaces[x],)) for i in range(ncomp):
for x in range(nsigma)] op = FFTSmoothingOperator(self._domain, sigma[i], space=spaces[i])
self._composed_exposure = ComposedOperator(kernel_exposure) self._kernel = op if self._kernel is None else op*self._kernel
self._exposure = None
for i in range(ncomp):
if np.isscalar(exposure[i]):
op = ScalingOperator(exposure[i], self._domain)
elif isinstance(exposure[i], Field):
op = DiagonalOperator(exposure[i], domain=self._domain,
spaces=(spaces[i],))
self._exposure = op if self._exposure is None\
else op*self._exposure
target_list = [FieldArray(self._domain[i].shape) for i in spaces] target_list = [FieldArray(self._domain[i].shape) for i in spaces]
self._target = DomainTuple.make(target_list) self._target = DomainTuple.make(target_list)
...@@ -81,19 +74,17 @@ class ResponseOperator(LinearOperator): ...@@ -81,19 +74,17 @@ class ResponseOperator(LinearOperator):
return self.TIMES | self.ADJOINT_TIMES return self.TIMES | self.ADJOINT_TIMES
def _times(self, x): def _times(self, x):
res = self._composed_kernel.times(x) res = self._exposure(self._kernel(x))
res = self._composed_exposure.times(res)
# removing geometric information # removing geometric information
return Field(self._target, val=res.val) return Field(self._target, val=res.val)
def _adjoint_times(self, x): def _adjoint_times(self, x):
# setting correct spaces # setting correct spaces
res = Field(self.domain, val=x.val) res = Field(self.domain, val=x.val)
res = self._composed_exposure.adjoint_times(res) res = self._exposure.adjoint_times(res)
res = res.weight(power=-1) res = res.weight(power=-1)
return self._composed_kernel.adjoint_times(res) return self._kernel.adjoint_times(res)
def apply(self, x, mode): def apply(self, x, mode):
self._check_input(x, mode) self._check_input(x, mode)
return self._times(x) if mode == self.TIMES else self._adjoint_times(x) return self._times(x) if mode == self.TIMES else self._adjoint_times(x)
...@@ -38,6 +38,7 @@ class SmoothnessOperator(EndomorphicOperator): ...@@ -38,6 +38,7 @@ class SmoothnessOperator(EndomorphicOperator):
def domain(self): def domain(self):
return self._laplace._domain return self._laplace._domain
# MR FIXME: shouldn't this operator actually be self-adjoint?
@property @property
def capability(self): def capability(self):
return self.TIMES return self.TIMES
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
# and financially supported by the Studienstiftung des deutschen Volkes. # and financially supported by the Studienstiftung des deutschen Volkes.
import numpy as np import numpy as np
from . import Space, PowerSpace, Field, ComposedOperator, DiagonalOperator,\ from . import Space, PowerSpace, Field, DiagonalOperator,\
PowerProjectionOperator, FFTOperator, sqrt, DomainTuple, dobj,\ PowerProjectionOperator, FFTOperator, sqrt, DomainTuple, dobj,\
utilities utilities
...@@ -241,11 +241,10 @@ def create_power_operator(domain, power_spectrum, space=None, dtype=None): ...@@ -241,11 +241,10 @@ def create_power_operator(domain, power_spectrum, space=None, dtype=None):
def create_composed_fft_operator(domain, codomain=None, all_to='other'): def create_composed_fft_operator(domain, codomain=None, all_to='other'):
fft_op_list = []
if codomain is None: if codomain is None:
codomain = [None]*len(domain) codomain = [None]*len(domain)
tdom = list(domain) tdom = list(domain)
res = None
for i, space in enumerate(domain): for i, space in enumerate(domain):
if not isinstance(space, Space): if not isinstance(space, Space):
continue continue
...@@ -256,6 +255,9 @@ def create_composed_fft_operator(domain, codomain=None, all_to='other'): ...@@ -256,6 +255,9 @@ def create_composed_fft_operator(domain, codomain=None, all_to='other'):
tgt = domain[i].get_default_codomain() tgt = domain[i].get_default_codomain()
else: else:
tgt = codomain[i] tgt = codomain[i]
fft_op_list += [FFTOperator(domain=tdom, target=tgt, space=i)] op = FFTOperator(domain=tdom, target=tgt, space=i)
res = op if res is None else op*res
tdom[i] = tgt tdom[i] = tgt
return ComposedOperator(fft_op_list) if res is None:
raise ValueError("empty operator")
return res
...@@ -17,7 +17,7 @@ class ComposedOperator_Tests(unittest.TestCase): ...@@ -17,7 +17,7 @@ class ComposedOperator_Tests(unittest.TestCase):
op1 = ift.DiagonalOperator(diag1, cspace, spaces=(0,)) op1 = ift.DiagonalOperator(diag1, cspace, spaces=(0,))
op2 = ift.DiagonalOperator(diag2, cspace, spaces=(1,)) op2 = ift.DiagonalOperator(diag2, cspace, spaces=(1,))
op = ift.ComposedOperator((op1, op2)) op = op2*op1
rand1 = ift.Field.from_random('normal', domain=(space1, space2)) rand1 = ift.Field.from_random('normal', domain=(space1, space2))
rand2 = ift.Field.from_random('normal', domain=(space1, space2)) rand2 = ift.Field.from_random('normal', domain=(space1, space2))
...@@ -34,7 +34,7 @@ class ComposedOperator_Tests(unittest.TestCase): ...@@ -34,7 +34,7 @@ class ComposedOperator_Tests(unittest.TestCase):