diff --git a/README.md b/README.md index 4a3cabfab3d4fe5a96a584a179663b5c6c956ac9..627745c90ddc24d5c7569d7674387589baf20fb9 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ MPI support is added via: ### Running the tests -In oder to run the tests one needs two additional packages: +To run the tests, additional packages are required: + sudo apt-get install python3-coverage python3-parameterized python3-pytest python3-pytest-cov Afterwards the tests (including a coverage report) can be run using the diff --git a/demos/bernoulli_demo.py b/demos/bernoulli_demo.py index 7caf6289893ccf69ad8a3a42c5b0b689f4d7f368..27bb850e26add5310c328493e090a44f7936cf4e 100644 --- a/demos/bernoulli_demo.py +++ b/demos/bernoulli_demo.py @@ -54,7 +54,7 @@ if __name__ == '__main__': A = ift.create_power_operator(harmonic_space, sqrtpspec) # Set up a sky operator and instrumental response - sky = ift.positive_tanh(HT(A)) + sky = ift.sigmoid(HT(A)) GR = ift.GeometryRemover(position_space) R = GR diff --git a/demos/getting_started_3.py b/demos/getting_started_3.py index 5e218f81812c6fed987b26740d6f1e391d9cd94e..bf435fec4e9af306ca17bf33e7a038171c700aa5 100644 --- a/demos/getting_started_3.py +++ b/demos/getting_started_3.py @@ -76,7 +76,7 @@ if __name__ == '__main__': # correlated_field = ift.CorrelatedField(position_space, A) # Apply a nonlinearity - signal = ift.positive_tanh(correlated_field) + signal = ift.sigmoid(correlated_field) # Build the line-of-sight response and define signal response LOS_starts, LOS_ends = random_los(100) if mode == 1 else radial_los(100) diff --git a/docs/source/code.rst b/docs/source/code.rst index 205080c4b7e0e3b3c6a37f225d92a4ae18f69784..ae1900660e4d54be1cf6728621a300868b8c7d16 100644 --- a/docs/source/code.rst +++ b/docs/source/code.rst @@ -136,7 +136,7 @@ A :class:`Field` object consists of the following components: - a data type (e.g. numpy.float64) - an array containing the actual values -Usually, the array is stored in the for of a ``numpy.ndarray``, but for very +Usually, the array is stored in the form of a ``numpy.ndarray``, but for very resource-intensive tasks NIFTy also provides an alternative storage method to be used with distributed memory processing. diff --git a/nifty5/data_objects/distributed_do.py b/nifty5/data_objects/distributed_do.py index eabbc8fe9cd9a804ec0ffd74d408cc9ac3a11a02..bbd56e3cc561b0d76983c69f71aa65bfd3505b8c 100644 --- a/nifty5/data_objects/distributed_do.py +++ b/nifty5/data_objects/distributed_do.py @@ -31,7 +31,8 @@ __all__ = ["ntask", "rank", "master", "local_shape", "data_object", "full", "redistribute", "default_distaxis", "is_numpy", "absmax", "norm", "lock", "locked", "uniform_full", "transpose", "to_global_data_rw", "ensure_not_distributed", "ensure_default_distributed", - "clipped_exp"] + "tanh", "conjugate", "sin", "cos", "tan", + "sinh", "cosh", "sinc", "absolute", "sign", "clip"] _comm = MPI.COMM_WORLD ntask = _comm.Get_size() @@ -211,6 +212,9 @@ class data_object(object): else: return data_object(self._shape, tval, self._distaxis) + def clip(self, min=None, max=None): + return data_object(self._shape, np.clip(self._data, min, max)) + def __neg__(self): return data_object(self._shape, -self._data, self._distaxis) @@ -292,7 +296,8 @@ def _math_helper(x, function, out): _current_module = sys.modules[__name__] -for f in ["sqrt", "exp", "log", "tanh", "conjugate"]: +for f in ["sqrt", "exp", "log", "tanh", "conjugate", "sin", "cos", "tan", + "sinh", "cosh", "sinc", "absolute", "sign"]: def func(f): def func2(x, out=None): return _math_helper(x, f, out) @@ -300,8 +305,8 @@ for f in ["sqrt", "exp", "log", "tanh", "conjugate"]: setattr(_current_module, f, func(f)) -def clipped_exp(a): - return data_object(x.shape, np.exp(np.clip(x.data, -300, 300), x.distaxis)) +def clip(x, a_min=None, a_max=None): + return data_object(x.shape, np.clip(x.data, a_min, a_max), x.distaxis) def from_object(object, dtype, copy, set_locked): diff --git a/nifty5/data_objects/numpy_do.py b/nifty5/data_objects/numpy_do.py index 95845666870926a089972488f53b6e5cbe815c05..4f30a27ddc23a26ea847432069cdbc782ebe8634 100644 --- a/nifty5/data_objects/numpy_do.py +++ b/nifty5/data_objects/numpy_do.py @@ -21,7 +21,8 @@ import numpy as np from numpy import empty, empty_like, exp, full, log from numpy import ndarray as data_object from numpy import ones, sqrt, tanh, vdot, zeros - +from numpy import sin, cos, tan, sinh, cosh, sinc +from numpy import absolute, sign, clip from .random import Random __all__ = ["ntask", "rank", "master", "local_shape", "data_object", "full", @@ -33,7 +34,8 @@ __all__ = ["ntask", "rank", "master", "local_shape", "data_object", "full", "redistribute", "default_distaxis", "is_numpy", "absmax", "norm", "lock", "locked", "uniform_full", "to_global_data_rw", "ensure_not_distributed", "ensure_default_distributed", - "clipped_exp"] + "clip", "sin", "cos", "tan", "sinh", + "cosh", "absolute", "sign", "sinc"] ntask = 1 rank = 0 @@ -149,7 +151,3 @@ def absmax(arr): def norm(arr, ord=2): return np.linalg.norm(arr.reshape(-1), ord=ord) - - -def clipped_exp(arr): - return np.exp(np.clip(arr, -300, 300)) diff --git a/nifty5/field.py b/nifty5/field.py index 6f2aed47c8ecbeb87d36b13abad22fc9b9217508..e0bbac119bc3d2851efb81005b4c92bdb13d3d4b 100644 --- a/nifty5/field.py +++ b/nifty5/field.py @@ -628,11 +628,14 @@ class Field(object): def flexible_addsub(self, other, neg): return self-other if neg else self+other - def positive_tanh(self): + def sigmoid(self): return 0.5*(1.+self.tanh()) - def clipped_exp(self): - return Field(self._domain, dobj.clipped_exp(self._val)) + def clip(self, min=None, max=None): + return Field(self._domain, dobj.clip(self._val, min, max)) + + def one_over(self): + return 1/self def _binary_op(self, other, op): # if other is a field, make sure that the domains match @@ -669,7 +672,9 @@ for op in ["__iadd__", "__isub__", "__imul__", "__idiv__", return func2 setattr(Field, op, func(op)) -for f in ["sqrt", "exp", "log", "tanh"]: +for f in ["sqrt", "exp", "log", "tanh", + "sin", "cos", "tan", "cosh", "sinh", + "absolute", "sinc", "sign"]: def func(f): def func2(self): return Field(self._domain, getattr(dobj, f)(self.val)) diff --git a/nifty5/library/los_response.py b/nifty5/library/los_response.py index c5e26c790de2b0154858f3b929d328ff9bc94720..9c7ab404f2bd3be2ffdadb586be33e121020192f 100644 --- a/nifty5/library/los_response.py +++ b/nifty5/library/los_response.py @@ -29,7 +29,7 @@ from ..operators.linear_operator import LinearOperator def _gaussian_error_function(x): - return 0.5*erfc(x*np.sqrt(2.)) + return 0.5/erfc(x*np.sqrt(2.)) def _comp_traverse(start, end, shp, dist, lo, mid, hi, erf): diff --git a/nifty5/linearization.py b/nifty5/linearization.py index 64bfb84cd3920832e99224f9c13ef544fb91bd30..3b7c5b486db5f7572d07f6512fd907e326433fb4 100644 --- a/nifty5/linearization.py +++ b/nifty5/linearization.py @@ -20,6 +20,7 @@ import numpy as np from .field import Field from .multi_field import MultiField from .sugar import makeOp +from .operators.scaling_operator import ScalingOperator class Linearization(object): @@ -196,23 +197,71 @@ class Linearization(object): tmp = self._val.exp() return self.new(tmp, makeOp(tmp)(self._jac)) - def clipped_exp(self): - tmp = self._val.clipped_exp() - return self.new(tmp, makeOp(tmp)(self._jac)) + def clip(self, min=None, max=None): + tmp = self._val.clip(min, max) + if (min is None) and (max is None): + return self + elif max is None: + tmp2 = makeOp(1. - (tmp == min)) + elif min is None: + tmp2 = makeOp(1. - (tmp == max)) + else: + tmp2 = makeOp(1. - (tmp == min) - (tmp == max)) + return self.new(tmp, tmp2(self._jac)) + + def sin(self): + tmp = self._val.sin() + tmp2 = self._val.cos() + return self.new(tmp, makeOp(tmp2)(self._jac)) + + def cos(self): + tmp = self._val.cos() + tmp2 = - self._val.sin() + return self.new(tmp, makeOp(tmp2)(self._jac)) + + def tan(self): + tmp = self._val.tan() + tmp2 = 1./(self._val.cos()**2) + return self.new(tmp, makeOp(tmp2)(self._jac)) + + def sinc(self): + tmp = self._val.sinc() + tmp2 = (self._val.cos()-tmp)/self._val + return self.new(tmp, makeOp(tmp2)(self._jac)) def log(self): tmp = self._val.log() return self.new(tmp, makeOp(1./self._val)(self._jac)) + def sinh(self): + tmp = self._val.sinh() + tmp2 = self._val.cosh() + return self.new(tmp, makeOp(tmp2)(self._jac)) + + def cosh(self): + tmp = self._val.cosh() + tmp2 = self._val.sinh() + return self.new(tmp, makeOp(tmp2)(self._jac)) + def tanh(self): tmp = self._val.tanh() return self.new(tmp, makeOp(1.-tmp**2)(self._jac)) - def positive_tanh(self): + def sigmoid(self): tmp = self._val.tanh() tmp2 = 0.5*(1.+tmp) return self.new(tmp2, makeOp(0.5*(1.-tmp**2))(self._jac)) + def absolute(self): + tmp = self._val.absolute() + tmp2 = self._val.sign() + return self.new(tmp, makeOp(tmp2)(self._jac)) + + def one_over(self): + tmp = 1./self._val + tmp2 = - tmp/self._val + return self.new(tmp, makeOp(tmp2)(self._jac)) + def add_metric(self, metric): return self.new(self._val, self._jac, metric) diff --git a/nifty5/multi_field.py b/nifty5/multi_field.py index a8d6b6e3ad0a480dc711471d93fa903b3d8e0529..dc50e32940b8f6dafdbd91844c462d7d7334d492 100644 --- a/nifty5/multi_field.py +++ b/nifty5/multi_field.py @@ -190,6 +190,10 @@ class MultiField(object): def conjugate(self): return self._transform(lambda x: x.conjugate()) + def clip(self, min=None, max=None): + return MultiField(self._domain, + tuple(clip(v, min, max) for v in self._val)) + def all(self): for v in self._val: if not v.all(): @@ -292,7 +296,7 @@ for op in ["__iadd__", "__isub__", "__imul__", "__idiv__", setattr(MultiField, op, func(op)) -for f in ["sqrt", "exp", "log", "tanh", "clipped_exp"]: +for f in ["sqrt", "exp", "log", "tanh"]: def func(f): def func2(self): fu = getattr(Field, f) diff --git a/nifty5/operators/operator.py b/nifty5/operators/operator.py index db8776c1a1de073ee058eaebe6df8dc921f31c2f..db78798a461535e108fffe37160e7d6bf806c990 100644 --- a/nifty5/operators/operator.py +++ b/nifty5/operators/operator.py @@ -94,6 +94,11 @@ class Operator(NiftyMetaBase()): return NotImplemented return _OpChain.make((_PowerOp(self.target, power), self)) + def clip(self, min=None, max=None): + if min is None and max is None: + return self + return _OpChain.make((_Clipper(self.target, min, max), self)) + def apply(self, x): raise NotImplementedError @@ -123,7 +128,8 @@ class Operator(NiftyMetaBase()): return self.__class__.__name__ -for f in ["sqrt", "exp", "log", "tanh", "positive_tanh", 'clipped_exp']: +for f in ["sqrt", "exp", "log", "tanh", "sigmoid", 'sin', 'cos', 'tan', + 'sinh', 'cosh', 'absolute', 'sinc', 'one_over']: def func(f): def func2(self): fa = _FunctionApplier(self.target, f) @@ -143,6 +149,18 @@ class _FunctionApplier(Operator): return getattr(x, self._funcname)() +class _Clipper(Operator): + def __init__(self, domain, min=None, max=None): + from ..sugar import makeDomain + self._domain = self._target = makeDomain(domain) + self._min = min + self._max = max + + def apply(self, x): + self._check_input(x) + return x.clip(self._min, self._max) + + class _PowerOp(Operator): def __init__(self, domain, power): from ..sugar import makeDomain diff --git a/nifty5/sugar.py b/nifty5/sugar.py index eae4c75955653eb84b31dcfc34587d924fac4259..ee2a224d9cf4f4ac287adccca8f6de938de628f7 100644 --- a/nifty5/sugar.py +++ b/nifty5/sugar.py @@ -33,7 +33,9 @@ from .operators.distributors import PowerDistributor __all__ = ['PS_field', 'power_analyze', 'create_power_operator', 'create_harmonic_smoothing_operator', 'from_random', 'full', 'from_global_data', 'from_local_data', - 'makeDomain', 'sqrt', 'exp', 'log', 'tanh', 'positive_tanh', + 'makeDomain', 'sqrt', 'exp', 'log', 'tanh', 'sigmoid', + 'sin', 'cos', 'tan', 'sinh', 'cosh', + 'absolute', 'one_over', 'clip', 'sinc', 'conjugate', 'get_signal_variance', 'makeOp', 'domain_union', 'get_default_codomain'] @@ -253,7 +255,9 @@ def domain_union(domains): _current_module = sys.modules[__name__] -for f in ["sqrt", "exp", "log", "tanh", "positive_tanh", "conjugate"]: +for f in ["sqrt", "exp", "log", "tanh", "sigmoid", + "conjugate", 'sin', 'cos', 'tan', 'sinh', 'cosh', + 'absolute', 'one_over', 'sinc']: def func(f): def func2(x): from .linearization import Linearization @@ -266,6 +270,10 @@ for f in ["sqrt", "exp", "log", "tanh", "positive_tanh", "conjugate"]: setattr(_current_module, f, func(f)) +def clip(a, a_min=None, a_max=None): + return a.clip(a_min, a_max) + + def get_default_codomain(domainoid, space=None): """For `RGSpace`, returns the harmonic partner domain. For `DomainTuple`, returns a copy of the object in which the domain diff --git a/test/test_energies/test_consistency.py b/test/test_energies/test_consistency.py index bf7d36978ad1eb44b577d2c6dbfdcef94d6a7359..906e61ce5aaa37d32828fab7adaa7b152e049568 100644 --- a/test/test_energies/test_consistency.py +++ b/test/test_energies/test_consistency.py @@ -111,7 +111,7 @@ class Energy_Tests(unittest.TestCase): def testBernoulli(self, space, seed): op = self.make_operator( space_key='s1', space=space, seed=seed)['s1'] - op = op.positive_tanh() + op = op.sigmoid() d = np.random.binomial(1, 0.1, size=space.shape) d = ift.Field.from_global_data(space, d) energy = ift.BernoulliEnergy(d) diff --git a/test/test_field.py b/test/test_field.py index 47d42d1389749ba14f3a1828166103ff79e5626c..59d380db66c21c5edaf0b87dd70a4b0c436072f3 100644 --- a/test/test_field.py +++ b/test/test_field.py @@ -291,3 +291,14 @@ class Test_Functionality(unittest.TestCase): assert_equal(f.local_data.shape, ()) assert_equal(f.local_data.size, 1) assert_equal(f.vdot(f), 9.) + + @expand(product([float(5), 5.], + [ift.RGSpace((8,), harmonic=True), ()], + ["exp", "log", "sin", "cos", "tan", "sinh", "cosh", "sinc", + "absolute", "sign"])) + def test_funcs(self, num, dom, func): + num = 5 + f = ift.Field.full(dom, num) + res = getattr(f, func)() + res2 = getattr(np, func)(num) + assert_allclose(res.local_data, res2) diff --git a/test/test_operators/test_operator_gradients.py b/test/test_operators/test_operator_gradients.py index 6f1208ab6c3559c2475c8b00552ea9161581d4be..6bf047a4c67d7283176b1bb5417990515ea5b25f 100644 --- a/test/test_operators/test_operator_gradients.py +++ b/test/test_operators/test_operator_gradients.py @@ -75,7 +75,7 @@ class OperatorTests(unittest.TestCase): op = ift.ScalingOperator(2.456, space)(select_s1*select_s2) pos = ift.from_random("normal", dom) ift.extra.check_value_gradient_consistency(op, pos, ntries=20) - op = ift.positive_tanh(ift.ScalingOperator(2.456, space)( + op = ift.sigmoid(ift.ScalingOperator(2.456, space)( select_s1*select_s2)) pos = ift.from_random("normal", dom) ift.extra.check_value_gradient_consistency(op, pos, ntries=20)