diff --git a/.gitignore b/.gitignore
index ea98e22b728117941248f28ab64cf202827b1219..81fc51b2496005396047398c8364e2095e70309c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,6 @@ build
 demos/*
 !demos/demo_faraday.py
 !demos/demo_faraday_map.npy
-!demos/demo_excaliwir.py
\ No newline at end of file
+!demos/demo_excaliwir.py
+!demos/demo_wf1.py
+!demos/demo_wf2.py
\ No newline at end of file
diff --git a/README.rst b/README.rst
index 09ed93af3f96b204ba8a4790d5b5cf62ba9db807..92e4c9831607b12b5d423098aa167572e3929b51 100644
--- a/README.rst
+++ b/README.rst
@@ -63,6 +63,7 @@ apply to fields.
         vector
     *   ``response_operator`` - exemplary responses that include a convolution,
         masking and projection
+    *   ``propagator_operator`` - information propagator in Wiener filter theory
     *   (and more)
 
 * (and more)
@@ -96,7 +97,7 @@ Requirements
 Download
 ........
 
-The latest release is tagged **v0.4.2** and is available as a source package
+The latest release is tagged **v0.6.0** and is available as a source package
 at `<https://github.com/mselig/nifty/tags>`_. The current version can be
 obtained by cloning the repository::
 
@@ -122,7 +123,7 @@ Please, acknowledge the use of NIFTY in your publication(s) by using a phrase
 such as the following:
 
     *"Some of the results in this publication have been derived using the NIFTY
-    [Selig et al., 2013] package."*
+    package [Selig et al., 2013]."*
 
 References
 ..........
diff --git a/__init__.py b/__init__.py
index deb66f085e0fd160477bc862d01de1f94443409f..fecc13a2660017c09850530995d2da9c5ffc36f4 100644
--- a/__init__.py
+++ b/__init__.py
@@ -23,6 +23,7 @@ from __future__ import division
 from nifty_core import *
 from nifty_cmaps import *
 from nifty_power import *
+from nifty_tools import *
 
 
 
diff --git a/demos/demo_excaliwir.py b/demos/demo_excaliwir.py
index 512d2ff02327ce4f7a2ec92f1f0db52bfee9593e..159c137cfaded69d5e4d33ef9a1e8d56249002e5 100644
--- a/demos/demo_excaliwir.py
+++ b/demos/demo_excaliwir.py
@@ -33,8 +33,6 @@
 """
 from __future__ import division
 from nifty import *
-from nifty.nifty_cmaps import *
-from nifty.nifty_power import *
 from scipy.sparse.linalg import LinearOperator as lo
 from scipy.sparse.linalg import cg
 
diff --git a/demos/demo_faraday.py b/demos/demo_faraday.py
index bc7f5db8a75437babecd94a12aea3128c2db6c94..b89b8a95454c76ca1784913819abe2e3d2690f20 100644
--- a/demos/demo_faraday.py
+++ b/demos/demo_faraday.py
@@ -39,7 +39,6 @@
 """
 from __future__ import division
 from nifty import *
-from nifty.nifty_cmaps import *
 
 
 about.warnings.off()
diff --git a/demos/demo_wf1.py b/demos/demo_wf1.py
new file mode 100644
index 0000000000000000000000000000000000000000..580f25ac8aecadb5e8c15045d5f9bccb025fe0ac
--- /dev/null
+++ b/demos/demo_wf1.py
@@ -0,0 +1,67 @@
+## NIFTY (Numerical Information Field Theory) has been developed at the
+## Max-Planck-Institute for Astrophysics.
+##
+## Copyright (C) 2013 Max-Planck-Society
+##
+## Author: Marco Selig
+## Project homepage: <http://www.mpa-garching.mpg.de/ift/nifty/>
+##
+## 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/>.
+
+"""
+    ..                  __   ____   __
+    ..                /__/ /   _/ /  /_
+    ..      __ ___    __  /  /_  /   _/  __   __
+    ..    /   _   | /  / /   _/ /  /   /  / /  /
+    ..   /  / /  / /  / /  /   /  /_  /  /_/  /
+    ..  /__/ /__/ /__/ /__/    \___/  \___   /  demo
+    ..                               /______/
+
+    NIFTY demo applying a Wiener filter using conjugate gradient.
+
+"""
+from __future__ import division
+from nifty import *                                                   # version 0.6.0
+
+
+# some signal space; e.g., a two-dimensional regular grid
+x_space = rg_space([256, 256])                                        # define signal space
+
+k_space = x_space.get_codomain()                                      # get conjugate space
+
+# some power spectrum
+power = (lambda k: 42 / (k + 1) ** 3)
+
+S = power_operator(k_space, spec=power)                               # define signal covariance
+s = S.get_random_field(domain=x_space)                                # generate signal
+
+R = response_operator(x_space, sigma=0.0, mask=1.0, assign=None)      # define response
+d_space = R.target                                                    # get data space
+
+# some noise variance; e.g., 100
+N = diagonal_operator(d_space, diag=100, bare=True)                   # define noise covariance
+n = N.get_random_field(domain=d_space)                                # generate noise
+
+d = R(s) + n                                                          # compute data
+
+j = R.adjoint_times(N.inverse_times(d))                               # define information source
+D = propagator_operator(S=S, N=N, R=R)                                # define information propagator
+
+m = D(j, tol=1E-4, note=True)                                         # reconstruct map
+
+s.plot(title="signal")                                                # plot signal
+d_ = field(x_space, val=d.val, target=k_space)
+d_.plot(title="data", vmin=s.val.min(), vmax=s.val.max())             # plot data
+m.plot(title="reconstructed map", vmin=s.val.min(), vmax=s.val.max()) # plot map
+
diff --git a/demos/demo_wf2.py b/demos/demo_wf2.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d0b0350094f12de19a68c0ab0b68e8d0467af0a
--- /dev/null
+++ b/demos/demo_wf2.py
@@ -0,0 +1,80 @@
+## NIFTY (Numerical Information Field Theory) has been developed at the
+## Max-Planck-Institute for Astrophysics.
+##
+## Copyright (C) 2013 Max-Planck-Society
+##
+## Author: Marco Selig
+## Project homepage: <http://www.mpa-garching.mpg.de/ift/nifty/>
+##
+## 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/>.
+
+"""
+    ..                  __   ____   __
+    ..                /__/ /   _/ /  /_
+    ..      __ ___    __  /  /_  /   _/  __   __
+    ..    /   _   | /  / /   _/ /  /   /  / /  /
+    ..   /  / /  / /  / /  /   /  /_  /  /_/  /
+    ..  /__/ /__/ /__/ /__/    \___/  \___   /  demo
+    ..                               /______/
+
+    NIFTY demo applying a Wiener filter using steepest descent.
+
+"""
+from __future__ import division
+from nifty import *                                                   # version 0.6.0
+
+
+# some signal space; e.g., a two-dimensional regular grid
+x_space = rg_space([256, 256])                                        # define signal space
+
+k_space = x_space.get_codomain()                                      # get conjugate space
+
+# some power spectrum
+power = (lambda k: 42 / (k + 1) ** 3)
+
+S = power_operator(k_space, spec=power)                               # define signal covariance
+s = S.get_random_field(domain=x_space)                                # generate signal
+
+R = response_operator(x_space, sigma=0.0, mask=1.0, assign=None)      # define response
+d_space = R.target                                                    # get data space
+
+# some noise variance; e.g., 100
+N = diagonal_operator(d_space, diag=100, bare=True)                   # define noise covariance
+n = N.get_random_field(domain=d_space)                                # generate noise
+
+d = R(s) + n                                                          # compute data
+
+j = R.adjoint_times(N.inverse_times(d))                               # define information source
+D = propagator_operator(S=S, N=N, R=R)                                # define information propagator
+
+
+def eggs(x):
+    """
+        Calculation of the information Hamiltonian and its gradient.
+
+    """
+    DIx = D.inverse_times(x)
+    H = 0.5 * DIx.dot(x) - j.dot(x)                                    # compute information Hamiltonian
+    g = DIx - j                                                        # compute its gradient
+    return H, g
+
+
+m = field(x_space, target=k_space)                                    # reconstruct map
+m,convergence = steepest_descent(eggs=eggs, note=True)(m, tol=1E-4, clevel=3)
+
+s.plot(title="signal")                                                # plot signal
+d_ = field(x_space, val=d.val, target=k_space)
+d_.plot(title="data", vmin=s.val.min(), vmax=s.val.max())             # plot data
+m.plot(title="reconstructed map", vmin=s.val.min(), vmax=s.val.max()) # plot map
+
diff --git a/nifty_core.py b/nifty_core.py
index 3efbc4eb22d49e416a65533e788c6e88420769ea..10aebc13cb9ed14614a5d9de883e2dce0b959d15 100644
--- a/nifty_core.py
+++ b/nifty_core.py
@@ -122,6 +122,8 @@ import pylab as pl
 from matplotlib.colors import LogNorm as ln
 from matplotlib.ticker import LogFormatter as lf
 from multiprocessing import Pool as mp
+from multiprocessing import Value as mv
+from multiprocessing import Array as ma
 ## third party libraries
 import gfft as gf
 import healpy as hp
@@ -484,7 +486,7 @@ class _about(object): ## nifty support class for global settings
 
         """
         ## version
-        self._version = "0.4.2"
+        self._version = "0.6.0"
 
         ## switches and notifications
         self._errors = notification(default=True,ccode=notification._code)
@@ -730,9 +732,9 @@ class random(object):
                         spec = domain.enforce_power(kwargs.get("spec",1),size=len(kindex),kindex=kindex,codomain=codomain)
                         kpack = [codomain.power_indices.get("pindex"),kindex]
                 else:
-                    kindex = domain.power_indixes.get("kindex")
+                    kindex = domain.power_indices.get("kindex")
                     spec = domain.enforce_power(kwargs.get("spec",1),size=len(kindex),kindex=kindex)
-                    kpack = [domain.power_indixes.get("pindex"),kindex]
+                    kpack = [domain.power_indices.get("pindex"),kindex]
             return [key,spec,kpack]
 
         elif(key=="uni"):
@@ -1126,7 +1128,7 @@ class space(object):
             Indexing with the unindexing array undoes the indexing with the
             indexing array; i.e., ``power == power[pindex].flatten()[pundex]``.
 
-            See also
+            See Also
             --------
             get_power_index
 
@@ -1161,7 +1163,7 @@ class space(object):
             -------
             None
 
-            See also
+            See Also
             --------
             get_power_indices
 
@@ -1213,7 +1215,7 @@ class space(object):
             Indexing with the unindexing array undoes the indexing with the
             indexing array; i.e., ``power == power[pindex].flatten()[pundex]``.
 
-            See also
+            See Also
             --------
             set_power_indices
 
@@ -1489,13 +1491,17 @@ class space(object):
 
             Returns
             -------
-            dot : float
+            dot : scalar
                 Inner product of the two arrays.
         """
         x = self.enforce_shape(np.array(x,dtype=self.datatype))
         y = self.enforce_shape(np.array(y,dtype=self.datatype))
         ## inner product
-        return np.dot(np.conjugate(x),y,out=None)
+        dot = np.dot(np.conjugate(x),y,out=None)
+        if(np.isreal(dot)):
+            return np.asscalar(np.real(dot))
+        else:
+            return dot
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -2130,6 +2136,8 @@ class point_space(space):
         if(bool(kwargs.get("save",False))):
             fig.savefig(str(kwargs.get("save")),dpi=None,facecolor=None,edgecolor=None,orientation='portrait',papertype=None,format=None,transparent=False,bbox_inches=None,pad_inches=0.1)
             pl.close(fig)
+        else:
+            fig.canvas.draw()
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -2151,7 +2159,7 @@ class rg_space(space):
         ..    /   __/ /   _   /
         ..   /  /    /  /_/  /
         ..  /__/     \____  /  space class
-        ..           /_____/
+        ..          /______/
 
         NIFTY subclass for spaces of regular Cartesian grids.
 
@@ -2416,7 +2424,7 @@ class rg_space(space):
             if(kindex is None):
                 ## quick kindex
                 if(self.fourier)and(not hasattr(self,"power_indices"))and(len(kwargs)==0):
-                    kindex = gp.nklength(gp.nkdict(self.para[:(np.size(self.para)-1)//2],self.vol,fourier=True))
+                    kindex = gp.nklength(gp.nkdict_fast(self.para[:(np.size(self.para)-1)//2],self.vol,fourier=True))
                 ## implicit kindex
                 else:
                     try:
@@ -2530,7 +2538,7 @@ class rg_space(space):
             -------
             None
 
-            See also
+            See Also
             --------
             get_power_indices
 
@@ -2894,13 +2902,22 @@ class rg_space(space):
 
             Returns
             -------
-            dot : float
+            dot : scalar
                 Inner product of the two arrays.
         """
         x = self.enforce_shape(np.array(x,dtype=self.datatype))
         y = self.enforce_shape(np.array(y,dtype=self.datatype))
         ## inner product
-        return np.dot(np.conjugate(x.flatten(order='C')),y.flatten(order='C'),out=None)
+        dot = np.dot(np.conjugate(x.flatten(order='C')),y.flatten(order='C'),out=None)
+        if(np.isreal(dot)):
+            return np.asscalar(np.real(dot))
+        elif(self.para[(np.size(self.para)-1)//2]!=2):
+                ## check imaginary part
+                if(np.absolute(dot.imag)>self.epsilon**2*np.absolute(dot.real)):
+                    about.warnings.cprint("WARNING: discarding considerable imaginary part.")
+                return np.asscalar(np.real(dot))
+        else:
+            return dot
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -3311,6 +3328,8 @@ class rg_space(space):
         if(bool(kwargs.get("save",False))):
             fig.savefig(str(kwargs.get("save")),dpi=None,facecolor=None,edgecolor=None,orientation='portrait',papertype=None,format=None,transparent=False,bbox_inches=None,pad_inches=0.1)
             pl.close(fig)
+        else:
+            fig.canvas.draw()
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -3613,7 +3632,7 @@ class lm_space(space):
             -------
             None
 
-            See also
+            See Also
             --------
             get_power_indices
 
@@ -3895,12 +3914,11 @@ class lm_space(space):
 
             Returns
             -------
-            dot : float
+            dot : scalar
                 Inner product of the two arrays.
         """
         x = self.enforce_shape(np.array(x,dtype=self.datatype))
         y = self.enforce_shape(np.array(y,dtype=self.datatype))
-
         ## inner product
         if(self.datatype==np.complex64):
             return gl.dotlm_f(x,y,lmax=self.para[0],mmax=self.para[1])
@@ -4167,6 +4185,8 @@ class lm_space(space):
         if(bool(kwargs.get("save",False))):
             fig.savefig(str(kwargs.get("save")),dpi=None,facecolor=None,edgecolor=None,orientation='portrait',papertype=None,format=None,transparent=False,bbox_inches=None,pad_inches=0.1)
             pl.close(fig)
+        else:
+            fig.canvas.draw()
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -4836,6 +4856,8 @@ class gl_space(space):
         if(bool(kwargs.get("save",False))):
             fig.savefig(str(kwargs.get("save")),dpi=None,facecolor=None,edgecolor=None,orientation='portrait',papertype=None,format=None,transparent=False,bbox_inches=None,pad_inches=0.1)
             pl.close(fig)
+        else:
+            fig.canvas.draw()
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -5427,10 +5449,13 @@ class hp_space(space):
                 cmap = pl.cm.jet ## default
             cmap.set_under(color='k',alpha=0.0) ## transparent box
             hp.mollview(x,fig=None,rot=None,coord=None,unit=unit,xsize=800,title=title,nest=False,min=vmin,max=vmax,flip="astro",remove_dip=False,remove_mono=False,gal_cut=0,format="%g",format2="%g",cbar=cbar,cmap=cmap,notext=False,norm=norm,hold=False,margins=None,sub=None)
+            fig = pl.gcf()
 
         if(bool(kwargs.get("save",False))):
             fig.savefig(str(kwargs.get("save")),dpi=None,facecolor=None,edgecolor=None,orientation='portrait',papertype=None,format=None,transparent=False,bbox_inches=None,pad_inches=0.1)
             pl.close(fig)
+        else:
+            fig.canvas.draw()
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -5899,13 +5924,17 @@ class nested_space(space):
 
             Returns
             -------
-            dot : float
+            dot : scalar
                 Inner product of the two arrays.
         """
         x = self.enforce_shape(np.array(x,dtype=self.datatype))
         y = self.enforce_shape(np.array(y,dtype=self.datatype))
         ## inner product
-        return np.sum(np.conjugate(x)*y,axis=None,dtype=None,out=None)
+        dot = np.sum(np.conjugate(x)*y,axis=None,dtype=None,out=None)
+        if(np.isreal(dot)):
+            return np.asscalar(np.real(dot))
+        else:
+            return dot
 
     def calc_pseudo_dot(self,x,y,**kwargs):
         """
@@ -7073,7 +7102,7 @@ def cos(x):
         cosx : {scalar, array, field}
             Cosine of `x` to the specified base.
 
-        See also
+        See Also
         --------
         sin
         tan
@@ -7104,7 +7133,7 @@ def sin(x):
         sinx : {scalar, array, field}
             Sine of `x` to the specified base.
 
-        See also
+        See Also
         --------
         cos
         tan
@@ -7136,7 +7165,7 @@ def cosh(x):
         coshx : {scalar, array, field}
             cosh of `x` to the specified base.
 
-        See also
+        See Also
         --------
         sinh
         tanh
@@ -7168,7 +7197,7 @@ def sinh(x):
         sinhx : {scalar, array, field}
             sinh of `x` to the specified base.
 
-        See also
+        See Also
         --------
         cosh
         tanh
@@ -7200,7 +7229,7 @@ def tan(x):
         tanx : {scalar, array, field}
             Tangent of `x` to the specified base.
 
-        See also
+        See Also
         --------
         cos
         sin
@@ -7232,7 +7261,7 @@ def tanh(x):
         tanhx : {scalar, array, field}
             tanh of `x` to the specified base.
 
-        See also
+        See Also
         --------
         cosh
         sinh
@@ -7263,7 +7292,7 @@ def arccos(x):
         arccosx : {scalar, array, field}
             arccos of `x` to the specified base.
 
-        See also
+        See Also
         --------
         arcsin
         arctan
@@ -7295,7 +7324,7 @@ def arcsin(x):
         arcsinx : {scalar, array, field}
             Logarithm of `x` to the specified base.
 
-        See also
+        See Also
         --------
         arccos
         arctan
@@ -7327,7 +7356,7 @@ def arccosh(x):
         arccoshx : {scalar, array, field}
             arccos of `x` to the specified base.
 
-        See also
+        See Also
         --------
         arcsinh
         arctanh
@@ -7358,7 +7387,7 @@ def arcsinh(x):
         arcsinhx : {scalar, array, field}
             arcsinh of `x` to the specified base.
 
-        See also
+        See Also
         --------
         arccosh
         arctanh
@@ -7389,7 +7418,7 @@ def arctan(x):
         arctanx : {scalar, array, field}
             arctan of `x` to the specified base.
 
-        See also
+        See Also
         --------
         arccos
         arcsin
@@ -7420,7 +7449,7 @@ def arctanh(x):
         arctanhx : {scalar, array, field}
             arctanh of `x` to the specified base.
 
-        See also
+        See Also
         --------
         arccosh
         arcsinh
@@ -7437,8 +7466,6 @@ def arctanh(x):
     else:
         return np.arctanh(np.array(x))
 
-## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
 def sqrt(x):
     """
         Returns the square root of a given object.
@@ -7466,8 +7493,6 @@ def sqrt(x):
     else:
         return np.sqrt(np.array(x))
 
-## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
 def exp(x):
     """
         Returns the exponential of a given object.
@@ -7482,7 +7507,7 @@ def exp(x):
         expx : {scalar, array, field}
             Exponential of `x` to the specified base.
 
-        See also
+        See Also
         --------
         log
 
@@ -7514,7 +7539,7 @@ def log(x,base=None):
         logx : {scalar, array, field}
             Logarithm of `x` to the specified base.
 
-        See also
+        See Also
         --------
         exp
 
@@ -7541,8 +7566,6 @@ def log(x,base=None):
     else:
         raise ValueError(about._errors.cstring("ERROR: invalid input basis."))
 
-## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
 def conjugate(x):
     """
         Computes the complex conjugate of a given object.
@@ -7565,9 +7588,6 @@ def conjugate(x):
 ##-----------------------------------------------------------------------------
 
 
-##-----------------------------------------------------------------------------
-
-
 
 
 
@@ -7679,7 +7699,7 @@ class operator(object):
         self.uni = bool(uni)
 
         if(self.domain.discrete):
-            self.imp = False
+            self.imp = True
         else:
             self.imp = bool(imp)
 
@@ -7792,7 +7812,7 @@ class operator(object):
                 x_ = x.transform(target=domain,overwrite=False)
         ## weight if ...
         if(not self.imp)and(not domain.discrete)and(not inverse):
-                x_ = x_.weight(power=1,overwrite=False)
+            x_ = x_.weight(power=1,overwrite=False)
         return x_
 
     def _debriefing(self,x,x_,target,inverse): ## > evaluates x and x_ after `multiply`
@@ -7962,7 +7982,7 @@ class operator(object):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    def tr(self,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,loop=False):
+    def tr(self,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=1,var=False,loop=False,**kwargs):
         """
             Computes the trace of the operator
 
@@ -7984,7 +8004,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -8006,9 +8026,9 @@ class operator(object):
         """
         if(domain is None):
             domain = self.domain
-        return trace_probing(self,function=self.times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var)(loop=loop)
+        return trace_probing(self,function=self.times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var,**kwargs)(loop=loop)
 
-    def inverse_tr(self,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,loop=False):
+    def inverse_tr(self,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=1,var=False,loop=False,**kwargs):
         """
             Computes the trace of the inverse operator
 
@@ -8030,7 +8050,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: Nonoe)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -8052,11 +8072,11 @@ class operator(object):
         """
         if(domain is None):
             domain = self.target
-        return trace_probing(self,function=self.inverse_times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var)(loop=loop)
+        return trace_probing(self,function=self.inverse_times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var,**kwargs)(loop=loop)
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    def diag(self,bare=False,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,save=False,path="tmp",prefix="",loop=False):
+    def diag(self,bare=False,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=1,var=False,save=False,path="tmp",prefix="",loop=False,**kwargs):
         """
             Computes the diagonal of the operator via probing.
 
@@ -8082,7 +8102,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -8123,9 +8143,12 @@ class operator(object):
         """
         if(domain is None):
             domain = self.domain
-        diag = diagonal_probing(self,function=self.times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var,save=save,path=path,prefix=prefix)(loop=loop)
+        diag = diagonal_probing(self,function=self.times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var,save=save,path=path,prefix=prefix,**kwargs)(loop=loop)
+        if(diag is None):
+#            about.warnings.cprint("WARNING: forwarding 'NoneType'.")
+            return None
         ## weight if ...
-        if(not domain.discrete)and(bare):
+        elif(not domain.discrete)and(bare):
             if(isinstance(diag,tuple)): ## diag == (diag,variance)
                 return domain.calc_weight(diag[0],power=-1),domain.calc_weight(diag[1],power=-1)
             else:
@@ -8133,7 +8156,7 @@ class operator(object):
         else:
             return diag
 
-    def inverse_diag(self,bare=False,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,save=False,path="tmp",prefix="",loop=False):
+    def inverse_diag(self,bare=False,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=1,var=False,save=False,path="tmp",prefix="",loop=False,**kwargs):
         """
             Computes the diagonal of the inverse operator via probing.
 
@@ -8159,7 +8182,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -8200,9 +8223,12 @@ class operator(object):
         """
         if(domain is None):
             domain = self.target
-        diag = diagonal_probing(self,function=self.inverse_times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var,save=save,path=path,prefix=prefix)(loop=loop)
+        diag = diagonal_probing(self,function=self.inverse_times,domain=domain,target=target,random=random,ncpu=ncpu,nrun=nrun,nper=nper,var=var,save=save,path=path,prefix=prefix,**kwargs)(loop=loop)
+        if(diag is None):
+#            about.warnings.cprint("WARNING: forwarding 'NoneType'.")
+            return None
         ## weight if ...
-        if(not domain.discrete)and(bare):
+        elif(not domain.discrete)and(bare):
             if(isinstance(diag,tuple)): ## diag == (diag,variance)
                 return domain.calc_weight(diag[0],power=-1),domain.calc_weight(diag[1],power=-1)
             else:
@@ -8262,7 +8288,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             save : bool, *optional*
                 whether all individual probing results are saved or not
                 (default: False)
@@ -8298,7 +8324,11 @@ class operator(object):
         """
         if(domain is None):
             domain = self.domain
-        return field(domain,val=self.diag(bare=bare,domain=domain,target=target,var=False,**kwargs),target=target)
+        diag = self.diag(bare=bare,domain=domain,target=target,var=False,**kwargs)
+        if(diag is None):
+            about.warnings.cprint("WARNING: forwarding 'NoneType'.")
+            return None
+        return field(domain,val=diag,target=target)
 
     def inverse_hat(self,bare=False,domain=None,target=None,**kwargs):
         """
@@ -8326,7 +8356,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: 8)
+                number of tasks performed by one process (default: 1)
             save : bool, *optional*
                 whether all individual probing results are saved or not
                 (default: False)
@@ -8362,7 +8392,11 @@ class operator(object):
         """
         if(domain is None):
             domain = self.target
-        return field(domain,val=self.inverse_diag(bare=bare,domain=domain,target=target,var=False,**kwargs),target=target)
+        diag = self.inverse_diag(bare=bare,domain=domain,target=target,var=False,**kwargs)
+        if(diag is None):
+            about.warnings.cprint("WARNING: forwarding 'NoneType'.")
+            return None
+        return field(domain,val=diag,target=target)
 
     def hathat(self,domain=None,**kwargs):
         """
@@ -8386,7 +8420,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             save : bool, *optional*
                 whether all individual probing results are saved or not
                 (default: False)
@@ -8421,7 +8455,11 @@ class operator(object):
         """
         if(domain is None):
             domain = self.domain
-        return diagonal_operator(domain=domain,diag=self.diag(bare=False,domain=domain,var=False,**kwargs),bare=False)
+        diag = self.diag(bare=False,domain=domain,var=False,**kwargs)
+        if(diag is None):
+            about.warnings.cprint("WARNING: forwarding 'NoneType'.")
+            return None
+        return diagonal_operator(domain=domain,diag=diag,bare=False)
 
     def inverse_hathat(self,domain=None,**kwargs):
         """
@@ -8446,7 +8484,7 @@ class operator(object):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             save : bool, *optional*
                 whether all individual probing results are saved or not
                 (default: False)
@@ -8481,7 +8519,11 @@ class operator(object):
         """
         if(domain is None):
             domain = self.target
-        return diagonal_operator(domain=domain,diag=self.inverse_diag(bare=False,domain=domain,var=False,**kwargs),bare=False)
+        diag = self.inverse_diag(bare=False,domain=domain,var=False,**kwargs)
+        if(diag is None):
+            about.warnings.cprint("WARNING: forwarding 'NoneType'.")
+            return None
+        return diagonal_operator(domain=domain,diag=diag,bare=False)
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
@@ -8713,7 +8755,7 @@ class diagonal_operator(operator):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -8770,7 +8812,7 @@ class diagonal_operator(operator):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -8836,7 +8878,7 @@ class diagonal_operator(operator):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -8919,7 +8961,7 @@ class diagonal_operator(operator):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -9245,7 +9287,7 @@ class power_operator(diagonal_operator):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    def set_power(self,newspec,bare=None,pindex=None,**kwargs):
+    def set_power(self,newspec,bare=True,pindex=None,**kwargs):
         """
             Sets the power spectrum of the diagonal operator
 
@@ -9280,14 +9322,12 @@ class power_operator(diagonal_operator):
             binbounds : {list, array}, *optional*
                 User specific inner boundaries of the bins, which are preferred
                 over the above parameters; by default no binning is done
-                (default: None).            vmin : {scalar, list, ndarray, field}, *optional*
-                Lower limit of the uniform distribution if ``random == "uni"``
-                (default: 0).
+                (default: None).
 
         """
-        if(bare is None):
-            about.warnings.cprint("WARNING: bare keyword set to default.")
-            bare = True
+#        if(bare is None):
+#            about.warnings.cprint("WARNING: bare keyword set to default.")
+#            bare = True
         ## check implicit pindex
         if(pindex is None):
             try:
@@ -9630,13 +9670,14 @@ class projection_operator(operator):
 
             Parameters
             ----------
-            x : valid field
+            x : field
+                Valid input field.
             band : int, *optional*
-                Projection band whereon to project. (default: None)
+                Projection band whereon to project (default: None).
             bandsup: {integer, list/array of integers}, *optional*
                 List of projection bands whereon to project and which to sum
-                up. The `band` keyword is prefered over `bandsup`.
-                (default: None)
+                up. The `band` keyword is prefered over `bandsup`
+                (default: None).
 
             Returns
             -------
@@ -9729,7 +9770,7 @@ class projection_operator(operator):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             save : bool, *optional*
                 whether all individual probing results are saved or not
                 (default: False)
@@ -9749,6 +9790,8 @@ class projection_operator(operator):
         if(isinstance(x,operator)):
             ## compute non-bare diagonal of the operator x
             x = x.diag(bare=False,domain=self.domain,target=x.domain,var=False,**kwargs)
+            if(x is None):
+                raise TypeError(about._error.cstring("ERROR: 'NoneType' encountered."))
 
         elif(isinstance(x,field)):
             ## check domain
@@ -9905,7 +9948,7 @@ class vecvec_operator(operator):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -9964,7 +10007,7 @@ class vecvec_operator(operator):
             nrun : int, *optional*
                 total number of probes (default: 8)
             nper : int, *optional*
-                number of tasks performed by one process (default: None)
+                number of tasks performed by one process (default: 1)
             var : bool, *optional*
                 Indicates whether to additionally return the probing variance
                 or not (default: False).
@@ -10083,13 +10126,13 @@ class response_operator(operator):
         domain : space
             The space wherein valid arguments live.
         sym : bool
-            Indicates whether the operator is self-adjoint or not
+            Indicates whether the operator is self-adjoint or not.
         uni : bool
-            Indicates whether the operator is unitary or not
+            Indicates whether the operator is unitary or not.
         imp : bool
             Indicates whether the incorporation of volume weights in
             multiplications is already implemented in the `multiply`
-            instance methods or not
+            instance methods or not.
         target : space
             The space wherein the operator output lives
         sigma : float
@@ -10233,15 +10276,17 @@ class response_operator(operator):
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
     def _multiply(self,x,**kwargs): ## > applies the operator to a given field
-        ## weight
-        if(self.domain.discrete)and(self.den):
-            x_ = self.domain.calc_weight(x.val,power=1)
-        elif(not self.domain.discrete)and(not self.den):
-            x_ = self.domain.calc_weight(x.val,power=-1)
-        else:
-            x_ = x.val
+#        ## weight ## TODO: remove in future version
+#        if(self.domain.discrete)and(self.den):
+#            x_ = self.domain.calc_weight(x.val,power=1)
+#        elif(not self.domain.discrete)and(not self.den):
+#            x_ = self.domain.calc_weight(x.val,power=-1)
+#        else:
+#            x_ = x.val
+#        ## smooth
+#        x_ = self.domain.calc_smooth(x_.val,sigma=self.sigma)
         ## smooth
-        x_ = self.domain.calc_smooth(x_,sigma=self.sigma)
+        x_ = self.domain.calc_smooth(x.val,sigma=self.sigma)
         ## mask
         x_ = self.mask*x_
         ## assign
@@ -10255,15 +10300,54 @@ class response_operator(operator):
         x_ = self.mask*x_
         ## smooth
         x_ = self.domain.calc_smooth(x_,sigma=self.sigma)
-        ## weight
-        if(self.domain.discrete)and(self.den):
-            x_ = self.domain.calc_weight(x_,power=1)
-        elif(not self.domain.discrete)and(not self.den):
-            x_ = self.domain.calc_weight(x_,power=-1)
+#        ## weight ## TODO: remove in future version
+#        if(self.domain.discrete)and(self.den):
+#            x_ = self.domain.calc_weight(x_,power=1)
+#        elif(not self.domain.discrete)and(not self.den):
+#            x_ = self.domain.calc_weight(x_,power=-1)
         return x_
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
+    def _briefing(self,x,domain,inverse): ## > prepares x for `multiply`
+        ## inspect x
+        if(not isinstance(x,field)):
+            x_ = field(domain,val=x,target=None)
+        else:
+            ## check x.domain
+            if(domain==x.domain):
+                x_ = x
+            ## transform
+            else:
+                x_ = x.transform(target=domain,overwrite=False)
+        ## weight if ...
+        if(not self.imp)and(not domain.discrete)and(not inverse)and(self.den): ## respect density
+            x_ = x_.weight(power=1,overwrite=False)
+        return x_
+
+    def _debriefing(self,x,x_,target,inverse): ## > evaluates x and x_ after `multiply`
+        if(x_ is None):
+            return None
+        else:
+            ## inspect x_
+            if(not isinstance(x_,field)):
+                x_ = field(target,val=x_,target=None)
+            elif(x_.domain!=target):
+                raise ValueError(about._errors.cstring("ERROR: invalid output domain."))
+            ## weight if ...
+            if(not self.imp)and(not target.discrete)and(not self.den^inverse): ## respect density
+                x_ = x_.weight(power=-1,overwrite=False)
+            ## inspect x
+            if(isinstance(x,field)):
+                ## repair ...
+                if(self.domain==self.target!=x.domain):
+                    x_ = x_.transform(target=x.domain,overwrite=False) ## ... domain
+                if(x_.domain==x.domain)and(x_.target!=x.target):
+                    x_.set_target(newtarget=x.target) ## ... codomain
+            return x_
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
     def __repr__(self):
         return "<nifty.response_operator>"
 
@@ -10275,6 +10359,1060 @@ class response_operator(operator):
 
 
 
+###=============================================================================
+#
+#class probing(object):
+#    """
+#        ..                                    __        __
+#        ..                                  /  /      /__/
+#        ..      ______    _____   ______   /  /___    __   __ ___    ____ __
+#        ..    /   _   | /   __/ /   _   | /   _   | /  / /   _   | /   _   /
+#        ..   /  /_/  / /  /    /  /_/  / /  /_/  / /  / /  / /  / /  /_/  /
+#        ..  /   ____/ /__/     \______/  \______/ /__/ /__/ /__/  \____  /  class
+#        .. /__/                                                  /______/
+#
+#        NIFTY class for probing (using multiprocessing)
+#
+#        This is the base NIFTY probing class from which other probing classes
+#        (e.g. diagonal probing) are derived.
+#
+#        When called, a probing class instance evaluates an operator or a
+#        function using random fields, whose components are random variables
+#        with mean 0 and variance 1. When an instance is called it returns the
+#        mean value of f(probe), where probe is a random field with mean 0 and
+#        variance 1. The mean is calculated as 1/N Sum[ f(probe_i) ].
+#
+#        Parameters
+#        ----------
+#        op : operator
+#            The operator specified by `op` is the operator to be probed.
+#            If no operator is given, then probing will be done by applying
+#            `function` to the probes. (default: None)
+#        function : function, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `function` is non optional. This is the function, that is applied
+#            to the probes. (default: `op.times`)
+#        domain : space, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `domain` is non optional. This is the space that the probes live
+#            in. (default: `op.domain`)
+#        target : domain, *optional*
+#            `target` is the codomain of `domain`
+#            (default: `op.domain.get_codomain()`)
+#        random : string, *optional*
+#            the distribution from which the probes are drawn. `random` can be
+#            either "pm1" or "gau". "pm1" is a uniform distribution over {+1,-1}
+#            or {+1,+i,-1,-i}, respectively. "gau" is a normal distribution with
+#            zero-mean and unit-variance (default: "pm1")
+#        ncpu : int, *optional*
+#            the number of cpus to be used from parallel probing. (default: 2)
+#        nrun : int, *optional*
+#            the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
+#            set to `ncpu**2`. (default: 8)
+#        nper : int, *optional*
+#            this number specifies how many probes will be evaluated by one
+#            worker. Afterwards a new worker will be created to evaluate a chunk
+#            of `nper` probes.
+#            If for example `nper=nrun/ncpu`, then every worker will be created
+#            for every cpu. This can lead to the case, that all workers but one
+#            are already finished, but have to wait for the last worker that
+#            might still have a considerable amount of evaluations left. This is
+#            obviously not very effective.
+#            If on the other hand `nper=1`, then for each evaluation a worker will
+#            be created. In this case all cpus will work until nrun probes have
+#            been evaluated.
+#            It is recommended to leave `nper` as the default value. (default: 8)
+#        var : bool, *optional*
+#            If `var` is True, then the variance of the sampled function will
+#            also be returned. The result is then a tuple with the mean in the
+#            zeroth entry and the variance in the first entry. (default: False)
+#
+#
+#        See Also
+#        --------
+#        diagonal_probing : A probing class to get the diagonal of an operator
+#        trace_probing : A probing class to get the trace of an operator
+#
+#
+#        Attributes
+#        ----------
+#        function : function
+#            the function, that is applied to the probes
+#        domain : space
+#            the space, where the probes live in
+#        target : space
+#            the codomain of `domain`
+#        random : string
+#            the random number generator used to create the probes
+#            (either "pm1" or "gau")
+#        ncpu : int
+#            the number of cpus used for probing
+#        nrun : int
+#            the number of probes to be evaluated, when the instance is called
+#        nper : int
+#            number of probes, that will be evaluated by one worker
+#        var : bool
+#            whether the variance will be additionally returned, when the
+#            instance is called
+#        quargs : dict
+#            Keyword arguments passed to `function` in each call.
+#
+#    """
+#    def __init__(self,op=None,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,**quargs):
+#        """
+#        initializes a probing instance
+#
+#        Parameters
+#        ----------
+#        op : operator
+#            The operator specified by `op` is the operator to be probed.
+#            If no operator is given, then probing will be done by applying
+#            `function` to the probes. (default: None)
+#        function : function, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `function` is non optional. This is the function, that is applied
+#            to the probes. (default: `op.times`)
+#        domain : space, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `domain` is non optional. This is the space that the probes live
+#            in. (default: `op.domain`)
+#        target : domain, *optional*
+#            `target` is the codomain of `domain`
+#            (default: `op.domain.get_codomain()`)
+#        random : string, *optional*
+#            the distribution from which the probes are drawn. `random` can be
+#            either "pm1" or "gau". "pm1" is a uniform distribution over {+1,-1}
+#            or {+1,+i,-1,-i}, respectively. "gau" is a normal distribution with
+#            zero-mean and unit-variance (default: "pm1")
+#        ncpu : int, *optional*
+#            the number of cpus to be used from parallel probing. (default: 2)
+#        nrun : int, *optional*
+#            the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
+#            set to `ncpu**2`. (default: 8)
+#        nper : int, *optional*
+#            this number specifies how many probes will be evaluated by one
+#            worker. Afterwards a new worker will be created to evaluate a chunk
+#            of `nper` probes.
+#            If for example `nper=nrun/ncpu`, then every worker will be created
+#            for every cpu. This can lead to the case, that all workers but one
+#            are already finished, but have to wait for the last worker that
+#            might still have a considerable amount of evaluations left. This is
+#            obviously not very effective.
+#            If on the other hand `nper=1`, then for each evaluation a worker will
+#            be created. In this case all cpus will work until nrun probes have
+#            been evaluated.
+#            It is recommended to leave `nper` as the default value. (default: 8)
+#        var : bool, *optional*
+#            If `var` is True, then the variance of the sampled function will
+#            also be returned. The result is then a tuple with the mean in the
+#            zeroth entry and the variance in the first entry. (default: False)
+#
+#        """
+#        if(op is None):
+#            ## check whether callable
+#            if(function is None)or(not hasattr(function,"__call__")):
+#                raise TypeError(about._errors.cstring("ERROR: invalid input."))
+#            ## check given domain
+#            if(domain is None)or(not isinstance(domain,space)):
+#                raise TypeError(about._errors.cstring("ERROR: invalid input."))
+#        else:
+#            if(not isinstance(op,operator)):
+#                raise TypeError(about._errors.cstring("ERROR: invalid input."))
+#            ## check whether callable
+#            if(function is None)or(not hasattr(function,"__call__")):
+#                function = op.times
+#            elif(op==function):
+#                function = op.times
+#            ## check whether correctly bound
+#            if(op!=function.im_self):
+#                raise NameError(about._errors.cstring("ERROR: invalid input."))
+#            ## check given domain
+#            if(domain is None)or(not isinstance(domain,space)):
+#                if(function in [op.inverse_times,op.adjoint_times]):
+#                    domain = op.target
+#                else:
+#                    domain = op.domain
+#            else:
+#                if(function in [op.inverse_times,op.adjoint_times]):
+#                    op.target.check_codomain(domain) ## a bit pointless
+#                else:
+#                    op.domain.check_codomain(domain) ## a bit pointless
+#
+#        self.function = function
+#        self.domain = domain
+#
+#        if(target is None):
+#            target = domain.get_codomain()
+#        ## check codomain
+#        self.domain.check_codomain(target) ## a bit pointless
+#        self.target = target
+#
+#        if(random not in ["pm1","gau"]):
+#            raise ValueError(about._errors.cstring("ERROR: unsupported random key '"+str(random)+"'."))
+#        self.random = random
+#
+#        self.ncpu = int(max(1,ncpu))
+#        self.nrun = int(max(self.ncpu**2,nrun))
+#        if(nper is None):
+#            self.nper = None
+#        else:
+#            self.nper = int(max(1,min(self.nrun//self.ncpu,nper)))
+#
+#        self.var = bool(var)
+#
+#        self.quargs = quargs
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def configure(self,**kwargs):
+#        """
+#            changes the attributes of the instance
+#
+#            Parameters
+#            ----------
+#            random : string, *optional*
+#                the random number generator used to create the probes (default: "pm1")
+#            ncpu : int, *optional*
+#                the number of cpus to be used for parallel probing. (default: 2)
+#            nrun : int, *optional*
+#                the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
+#                set to `ncpu**2`. (default: 8)
+#            nper : int, *optional*
+#                number of probes, that will be evaluated by one worker (default: 8)
+#            var : bool, *optional*
+#                whether the variance will be additionally returned (default: False)
+#
+#        """
+#        if("random" in kwargs):
+#            if(kwargs.get("random") not in ["pm1","gau"]):
+#                raise ValueError(about._errors.cstring("ERROR: unsupported random key '"+str(kwargs.get("random"))+"'."))
+#            self.random = kwargs.get("random")
+#
+#        if("ncpu" in kwargs):
+#            self.ncpu = int(max(1,kwargs.get("ncpu")))
+#        if("nrun" in kwargs):
+#            self.nrun = int(max(self.ncpu**2,kwargs.get("nrun")))
+#        if("nper" in kwargs):
+#            if(kwargs.get("nper") is None):
+#                self.nper = None
+#            else:
+#                self.nper = int(max(1,min(self.nrun//self.ncpu,kwargs.get("nper"))))
+#
+#        if("var" in kwargs):
+#            self.var = bool(kwargs.get("var"))
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def gen_probe(self):
+#        """
+#            Generates a single probe
+#
+#            Returns
+#            -------
+#            probe : field
+#                a random field living in `domain` with mean 0 and variance 1 in
+#                each component
+#
+#        """
+#        return field(self.domain,val=None,target=self.target,random=self.random)
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def probing(self,idnum,probe):
+#        """
+#            Computes a single probing result given one probe
+#
+#            Parameters
+#            ----------
+#            probe : field
+#                the field on which `function` will be applied
+#            idnum : int
+#                    the identification number of the probing
+#
+#            Returns
+#            -------
+#            result : array-like
+#                the result of applying `function` to `probe`. The exact type
+#                depends on the function.
+#
+#        """
+#        f = self.function(probe,**self.quargs)
+#        if(isinstance(f,field)):
+#            return f.val
+#        else:
+#            return f
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def evaluate(self,results):
+#        """
+#            evaluates the probing results
+#
+#            Parameters
+#            ----------
+#            results : list
+#                the list containing the results of the individual probings.
+#                The type of the list elements depends on the function.
+#
+#            Returns
+#            -------
+#            final : array-like
+#                    the final probing result. 1/N Sum[ probing(probe_i) ]
+#            var : array-like
+#                    the variance of the final probing result.
+#                (N(N-1))^(-1) Sum[ ( probing(probe_i) - final)^2 ]
+#                If the variance is returned, the return will be a tuple with
+#                `final` in the zeroth entry and `var` in the first entry.
+#
+#        """
+#        if(len(results)==0):
+#            about.warnings.cprint("WARNING: probing failed.")
+#            return None
+#        elif(self.var):
+#            return np.mean(np.array(results),axis=0,dtype=None,out=None),np.var(np.array(results),axis=0,dtype=None,out=None,ddof=0)/(len(results)-1)
+#        else:
+#            return np.mean(np.array(results),axis=0,dtype=None,out=None)
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def _progress(self,idnum): ## > prints progress status by in upto 10 dots
+#        tenths = 1+(10*idnum//self.nrun)
+#        about.infos.cflush(("\b")*10+('.')*tenths+(' ')*(10-tenths))
+#
+#    def _single_probing(self,zipped): ## > performs one probing operation
+#        ## generate probe
+#        np.random.seed(zipped[0])
+#        probe = self.gen_probe()
+#        ## do the actual probing
+#        self._progress(zipped[1])
+#        return self.probing(zipped[1],probe)
+#
+#    def _serial_probing(self,zipped): ## > performs the probing operation serially
+#        try:
+#            return self._single_probing(zipped)
+#        except:
+#            ## kill pool
+#            os.kill()
+#
+#    def _parallel_probing(self): ## > performs the probing operations in parallel
+#        ## define random seed
+#        seed = np.random.randint(10**8,high=None,size=self.nrun)
+#        ## build pool
+#        if(about.infos.status):
+#            so.write(about.infos.cstring("INFO: multiprocessing "+(' ')*10))
+#            so.flush()
+#        pool = mp(processes=self.ncpu,initializer=None,initargs=(),maxtasksperchild=self.nper)
+#        try:
+#            ## retrieve results
+#            results = pool.map(self._serial_probing,zip(seed,np.arange(self.nrun,dtype=np.int)),chunksize=None)#,callback=None).get(timeout=None) ## map_async replaced
+#            ## close and join pool
+#            about.infos.cflush(" done.")
+#            pool.close()
+#            pool.join()
+#        except:
+#            ## terminate and join pool
+#            pool.terminate()
+#            pool.join()
+#            raise Exception(about._errors.cstring("ERROR: unknown. NOTE: pool terminated.")) ## traceback by looping
+#        ## cleanup
+#        results = [rr for rr in results if(rr is not None)]
+#        if(len(results)<self.nrun):
+#            about.infos.cflush(" ( %u probe(s) failed, effectiveness == %.1f%% )\n"%(self.nrun-len(results),100*len(results)/self.nrun))
+#        else:
+#            about.infos.cflush("\n")
+#        ## evaluate
+#        return self.evaluate(results)
+#
+#    def _nonparallel_probing(self): ## > performs the probing operations one after another
+#        ## define random seed
+#        seed = np.random.randint(10**8,high=None,size=self.nrun)
+#        ## retrieve results
+#        if(about.infos.status):
+#            so.write(about.infos.cstring("INFO: looping "+(' ')*10))
+#            so.flush()
+#        results = map(self._single_probing,zip(seed,np.arange(self.nrun,dtype=np.int)))
+#        about.infos.cflush(" done.")
+#        ## cleanup
+#        results = [rr for rr in results if(rr is not None)]
+#        if(len(results)<self.nrun):
+#            about.infos.cflush(" ( %u probe(s) failed, effectiveness == %.1f%% )\n"%(self.nrun-len(results),100*len(results)/self.nrun))
+#        else:
+#            about.infos.cflush("\n")
+#        ## evaluate
+#        return self.evaluate(results)
+#
+#    def __call__(self,loop=False,**kwargs):
+#        """
+#
+#            Starts the probing process.
+#            All keyword arguments that can be given to `configure` can also be
+#            given to `__call__` and have the same effect.
+#
+#            Parameters
+#            ----------
+#            loop : bool, *optional*
+#                if `loop` is True, then multiprocessing will be disabled and
+#                all probes are evaluated by a single worker (default: False)
+#
+#            Returns
+#            -------
+#            results : see **Returns** in `evaluate`
+#
+#            other parameters
+#            ----------------
+#            kwargs : see **Parameters** in `configure`
+#
+#        """
+#        self.configure(**kwargs)
+#        if(not about.multiprocessing.status)or(loop):
+#            return self._nonparallel_probing()
+#        else:
+#            return self._parallel_probing()
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def __repr__(self):
+#        return "<nifty.probing>"
+#
+###=============================================================================
+#
+#
+#
+###-----------------------------------------------------------------------------
+#
+#class trace_probing(probing):
+#    """
+#        ..      __
+#        ..    /  /_
+#        ..   /   _/  _____   ____ __   _______   _______
+#        ..  /  /   /   __/ /   _   / /   ____/ /   __  /
+#        .. /  /_  /  /    /  /_/  / /  /____  /  /____/
+#        .. \___/ /__/     \______|  \______/  \______/  probing class
+#
+#        NIFTY subclass for trace probing (using multiprocessing)
+#
+#        When called, a trace_probing class instance samples the trace of an
+#        operator or a function using random fields, whose components are random
+#        variables with mean 0 and variance 1. When an instance is called it
+#        returns the mean value of the scalar product of probe and f(probe),
+#        where probe is a random        field with mean 0 and variance 1.
+#        The mean is calculated as 1/N Sum[ probe_i.dot(f(probe_i)) ].
+#
+#        Parameters
+#        ----------
+#        op : operator
+#            The operator specified by `op` is the operator to be probed.
+#            If no operator is given, then probing will be done by applying
+#            `function` to the probes. (default: None)
+#        function : function, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `function` is non optional. This is the function, that is applied
+#            to the probes. (default: `op.times`)
+#        domain : space, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `domain` is non optional. This is the space that the probes live
+#            in. (default: `op.domain`)
+#        target : domain, *optional*
+#            `target` is the codomain of `domain`
+#            (default: `op.domain.get_codomain()`)
+#        random : string, *optional*
+#            the distribution from which the probes are drawn. `random` can be
+#            either "pm1" or "gau". "pm1" is a uniform distribution over {+1,-1}
+#            or {+1,+i,-1,-i}, respectively. "gau" is a normal distribution with
+#            zero-mean and unit-variance (default: "pm1")
+#        ncpu : int, *optional*
+#            the number of cpus to be used from parallel probing. (default: 2)
+#        nrun : int, *optional*
+#            the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
+#            set to `ncpu**2`. (default: 8)
+#        nper : int, *optional*
+#            this number specifies how many probes will be evaluated by one
+#            worker. Afterwards a new worker will be created to evaluate a chunk
+#            of `nper` probes.
+#            If for example `nper=nrun/ncpu`, then every worker will be created
+#            for every cpu. This can lead to the case, that all workers but one
+#            are already finished, but have to wait for the last worker that
+#            might still have a considerable amount of evaluations left. This is
+#            obviously not very effective.
+#            If on the other hand `nper=1`, then for each evaluation a worker will
+#            be created. In this case all cpus will work until nrun probes have
+#            been evaluated.
+#            It is recommended to leave `nper` as the default value. (default: 8)
+#        var : bool, *optional*
+#            If `var` is True, then the variance of the sampled function will
+#            also be returned. The result is then a tuple with the mean in the
+#            zeroth entry and the variance in the first entry. (default: False)
+#
+#
+#        See Also
+#        --------
+#        probing : The base probing class
+#        diagonal_probing : A probing class to get the diagonal of an operator
+#        operator.tr : the trace function uses trace probing in the case of non
+#            diagonal operators
+#
+#
+#        Attributes
+#        ----------
+#        function : function
+#            the function, that is applied to the probes
+#        domain : space
+#            the space, where the probes live in
+#        target : space
+#            the codomain of `domain`
+#        random : string
+#            the random number generator used to create the probes
+#            (either "pm1" or "gau")
+#        ncpu : int
+#            the number of cpus used for probing
+#        nrun : int
+#            the number of probes to be evaluated, when the instance is called
+#        nper : int
+#            number of probes, that will be evaluated by one worker
+#        var : bool
+#            whether the variance will be additionally returned, when the
+#            instance is called
+#        quargs : dict
+#            Keyword arguments passed to `function` in each call.
+#
+#    """
+#    def __init__(self,op,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,**quargs):
+#        """
+#        initializes a trace probing instance
+#
+#        Parameters
+#        ----------
+#        op : operator
+#            The operator specified by `op` is the operator to be probed.
+#            If no operator is given, then probing will be done by applying
+#            `function` to the probes. (default: None)
+#        function : function, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `function` is non optional. This is the function, that is applied
+#            to the probes. (default: `op.times`)
+#        domain : space, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `domain` is non optional. This is the space that the probes live
+#            in. (default: `op.domain`)
+#        target : domain, *optional*
+#            `target` is the codomain of `domain`
+#            (default: `op.domain.get_codomain()`)
+#        random : string, *optional*
+#            the distribution from which the probes are drawn. `random` can be
+#            either "pm1" or "gau". "pm1" is a uniform distribution over {+1,-1}
+#            or {+1,+i,-1,-i}, respectively. "gau" is a normal distribution with
+#            zero-mean and unit-variance (default: "pm1")
+#        ncpu : int, *optional*
+#            the number of cpus to be used from parallel probing. (default: 2)
+#        nrun : int, *optional*
+#            the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
+#            set to `ncpu**2`. (default: 8)
+#        nper : int, *optional*
+#            this number specifies how many probes will be evaluated by one
+#            worker. Afterwards a new worker will be created to evaluate a chunk
+#            of `nper` probes.
+#            If for example `nper=nrun/ncpu`, then every worker will be created
+#            for every cpu. This can lead to the case, that all workers but one
+#            are already finished, but have to wait for the last worker that
+#            might still have a considerable amount of evaluations left. This is
+#            obviously not very effective.
+#            If on the other hand `nper=1`, then for each evaluation a worker will
+#            be created. In this case all cpus will work until nrun probes have
+#            been evaluated.
+#            It is recommended to leave `nper` as the default value. (default: 8)
+#        var : bool, *optional*
+#            If `var` is True, then the variance of the sampled function will
+#            also be returned. The result is then a tuple with the mean in the
+#            zeroth entry and the variance in the first entry. (default: False)
+#
+#        """
+#        if(not isinstance(op,operator)):
+#            raise TypeError(about._errors.cstring("ERROR: invalid input."))
+#        elif(op.nrow()!=op.ncol()):
+#            raise ValueError(about._errors.cstring("ERROR: trace ill-defined for "+str(op.nrow())+" x "+str(op.ncol())+" matrices."))
+#
+#        ## check whether callable
+#        if(function is None)or(not hasattr(function,"__call__")):
+#            function = op.times
+#        elif(op==function):
+#            function = op.times
+#        ## check whether correctly bound
+#        if(op!=function.im_self):
+#            raise NameError(about._errors.cstring("ERROR: invalid input."))
+#        self.function = function
+#
+#        ## check given domain
+#        if(domain is None)or(not isinstance(domain,space)):
+#            if(self.function in [op.inverse_times,op.adjoint_times]):
+#                domain = op.target
+#            else:
+#                domain = op.domain
+#        elif(not op.domain.check_codomain(domain))or(not op.target.check_codomain(domain)): ## restrictive
+#            raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+#        self.domain = domain
+#
+#        if(target is None):
+#            target = domain.get_codomain()
+#        ## check codomain
+#        self.domain.check_codomain(target) ## a bit pointless
+#        self.target = target
+#
+#        ## check degrees of freedom
+#        if(op.domain.dof()>self.domain.dof()):
+#            about.infos.cprint("INFO: variant numbers of degrees of freedom ( "+str(op.domain.dof())+" / "+str(self.domain.dof())+" ).")
+#
+#        if(random not in ["pm1","gau"]):
+#            raise ValueError(about._errors.cstring("ERROR: unsupported random key '"+str(random)+"'."))
+#        self.random = random
+#
+#        self.ncpu = int(max(1,ncpu))
+#        self.nrun = int(max(self.ncpu**2,nrun))
+#        if(nper is None):
+#            self.nper = None
+#        else:
+#            self.nper = int(max(1,min(self.nrun//self.ncpu,nper)))
+#
+#        self.var = bool(var)
+#
+#        self.quargs = quargs
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def probing(self,idnum,probe):
+#        """
+#            Computes a single probing result given one probe
+#
+#            Parameters
+#            ----------
+#            probe : field
+#                the field on which `function` will be applied
+#            idnum : int
+#                    the identification number of the probing
+#
+#            Returns
+#            -------
+#            result : float
+#                    the result of `probe.dot(function(probe))`
+#        """
+#        f = self.function(probe,**self.quargs)
+#        if(f is None):
+#            return None
+#        else:
+#            return self.domain.calc_dot(probe.val,f.val) ## discrete inner product
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def __repr__(self):
+#        return "<nifty.trace_probing>"
+#
+###-----------------------------------------------------------------------------
+#
+#
+#
+###-----------------------------------------------------------------------------
+#
+#class diagonal_probing(probing):
+#    """
+#        ..           __   __                                                     __
+#        ..         /  / /__/                                                   /  /
+#        ..    ____/  /  __   ____ __   ____ __   ______    __ ___    ____ __  /  /
+#        ..  /   _   / /  / /   _   / /   _   / /   _   | /   _   | /   _   / /  /
+#        .. /  /_/  / /  / /  /_/  / /  /_/  / /  /_/  / /  / /  / /  /_/  / /  /_
+#        .. \______| /__/  \______|  \___   /  \______/ /__/ /__/  \______|  \___/  probing class
+#        ..                         /______/
+#
+#        NIFTY subclass for diagonal probing (using multiprocessing)
+#
+#        When called, a diagonal_probing class instance samples the diagonal of
+#        an operator or a function using random fields, whose components are
+#        random variables with mean 0 and variance 1. When an instance is called
+#        it returns the mean value of probe*f(probe), where probe is a random
+#        field with mean 0 and variance 1.
+#        The mean is calculated as 1/N Sum[ probe_i*f(probe_i) ]
+#        ('*' denoting component wise multiplication)
+#
+#        Parameters
+#        ----------
+#        op : operator
+#            The operator specified by `op` is the operator to be probed.
+#            If no operator is given, then probing will be done by applying
+#            `function` to the probes. (default: None)
+#        function : function, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `function` is non optional. This is the function, that is applied
+#            to the probes. (default: `op.times`)
+#        domain : space, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `domain` is non optional. This is the space that the probes live
+#            in. (default: `op.domain`)
+#        target : domain, *optional*
+#            `target` is the codomain of `domain`
+#            (default: `op.domain.get_codomain()`)
+#        random : string, *optional*
+#            the distribution from which the probes are drawn. `random` can be
+#            either "pm1" or "gau". "pm1" is a uniform distribution over {+1,-1}
+#            or {+1,+i,-1,-i}, respectively. "gau" is a normal distribution with
+#            zero-mean and unit-variance (default: "pm1")
+#        ncpu : int, *optional*
+#            the number of cpus to be used for parallel probing. (default: 2)
+#        nrun : int, *optional*
+#            the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
+#            set to `ncpu**2`. (default: 8)
+#        nper : int, *optional*
+#            this number specifies how many probes will be evaluated by one
+#            worker. Afterwards a new worker will be created to evaluate a chunk
+#            of `nper` probes.
+#            If for example `nper=nrun/ncpu`, then every worker will be created
+#            for every cpu. This can lead to the case, that all workers but one
+#            are already finished, but have to wait for the last worker that
+#            might still have a considerable amount of evaluations left. This is
+#            obviously not very effective.
+#            If on the other hand `nper=1`, then for each evaluation a worker will
+#            be created. In this case all cpus will work until nrun probes have
+#            been evaluated.
+#            It is recommended to leave `nper` as the default value. (default: 8)
+#        var : bool, *optional*
+#            If `var` is True, then the variance of the sampled function will
+#            also be returned. The result is then a tuple with the mean in the
+#            zeroth entry and the variance in the first entry. (default: False)
+#        save : bool, *optional*
+#            If `save` is True, then the probing results will be written to the
+#            hard disk instead of being saved in the RAM. This is recommended
+#            for high dimensional fields whose probes would otherwise fill up
+#            the memory. (default: False)
+#        path : string, *optional*
+#            the path, where the probing results are saved, if `save` is True.
+#            (default: "tmp")
+#        prefix : string, *optional*
+#            a prefix for the saved probing results. The saved results will be
+#            named using that prefix and an 8-digit number
+#            (e.g. "<prefix>00000001.npy"). (default: "")
+#
+#
+#        See Also
+#        --------
+#        trace_probing : A probing class to get the trace of an operator
+#        probing : The base probing class
+#        operator.diag : The diag function uses diagonal probing in the case of
+#            non diagonal operators
+#        operator.hat : The hat function uses diagonal probing in the case of
+#            non diagonal operators
+#
+#
+#        Attributes
+#        ----------
+#        function : function
+#            the function, that is applied to the probes
+#        domain : space
+#            the space, where the probes live in
+#        target : space
+#            the codomain of `domain`
+#        random : string
+#            the random number generator used to create the probes
+#            (either "pm1" or "gau")
+#        ncpu : int
+#            the number of cpus used for probing
+#        nrun : int
+#            the number of probes to be evaluated, when the instance is called
+#        nper : int
+#            number of probes, that will be evaluated by one worker
+#        var : bool
+#            whether the variance will be additionally returned, when the
+#            instance is called
+#        save : {string, None}
+#            the path and prefix for saved probe files. None in the case where
+#            the probing results are stored in the RAM.
+#        quargs : dict
+#            Keyword arguments passed to `function` in each call.
+#
+#    """
+#    def __init__(self,op,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,save=False,path="tmp",prefix="",**quargs):
+#        """
+#        initializes a diagonal probing instance
+#
+#        Parameters
+#        ----------
+#        op : operator
+#            The operator specified by `op` is the operator to be probed.
+#            If no operator is given, then probing will be done by applying
+#            `function` to the probes. (default: None)
+#        function : function, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `function` is non optional. This is the function, that is applied
+#            to the probes. (default: `op.times`)
+#        domain : space, *optional*
+#            If no operator has been specified as `op`, then specification of
+#            `domain` is non optional. This is the space that the probes live
+#            in. (default: `op.domain`)
+#        target : domain, *optional*
+#            `target` is the codomain of `domain`
+#            (default: `op.domain.get_codomain()`)
+#        random : string, *optional*
+#            the distribution from which the probes are drawn. `random` can be
+#            either "pm1" or "gau". "pm1" is a uniform distribution over {+1,-1}
+#            or {+1,+i,-1,-i}, respectively. "gau" is a normal distribution with
+#            zero-mean and unit-variance (default: "pm1")
+#        ncpu : int, *optional*
+#            the number of cpus to be used for parallel probing. (default: 2)
+#        nrun : int, *optional*
+#            the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
+#            set to `ncpu**2`. (default: 8)
+#        nper : int, *optional*
+#            this number specifies how many probes will be evaluated by one
+#            worker. Afterwards a new worker will be created to evaluate a chunk
+#            of `nper` probes.
+#            If for example `nper=nrun/ncpu`, then every worker will be created
+#            for every cpu. This can lead to the case, that all workers but one
+#            are already finished, but have to wait for the last worker that
+#            might still have a considerable amount of evaluations left. This is
+#            obviously not very effective.
+#            If on the other hand `nper=1`, then for each evaluation a worker will
+#            be created. In this case all cpus will work until nrun probes have
+#            been evaluated.
+#            It is recommended to leave `nper` as the default value. (default: 8)
+#        var : bool, *optional*
+#            If `var` is True, then the variance of the sampled function will
+#            also be returned. The result is then a tuple with the mean in the
+#            zeroth entry and the variance in the first entry. (default: False)
+#        save : bool, *optional*
+#            If `save` is True, then the probing results will be written to the
+#            hard disk instead of being saved in the RAM. This is recommended
+#            for high dimensional fields whose probes would otherwise fill up
+#            the memory. (default: False)
+#        path : string, *optional*
+#            the path, where the probing results are saved, if `save` is True.
+#            (default: "tmp")
+#        prefix : string, *optional*
+#            a prefix for the saved probing results. The saved results will be
+#            named using that prefix and an 8-digit number
+#            (e.g. "<prefix>00000001.npy"). (default: "")
+#
+#        """
+#
+#        if(not isinstance(op,operator)):
+#            raise TypeError(about._errors.cstring("ERROR: invalid input."))
+#        elif(op.nrow()!=op.ncol()):
+#            raise ValueError(about._errors.cstring("ERROR: diagonal ill-defined for "+str(op.nrow())+" x "+str(op.ncol())+" matrices."))
+#
+#        ## check whether callable
+#        if(function is None)or(not hasattr(function,"__call__")):
+#            function = op.times
+#        elif(op==function):
+#            function = op.times
+#        ## check whether correctly bound
+#        if(op!=function.im_self):
+#            raise NameError(about._errors.cstring("ERROR: invalid input."))
+#        self.function = function
+#
+#        ## check given domain
+#        if(domain is None)or(not isinstance(domain,space)):
+#            if(self.function in [op.inverse_times,op.adjoint_times]):
+#                domain = op.target
+#            else:
+#                domain = op.domain
+#        elif(not op.domain.check_codomain(domain))or(not op.target.check_codomain(domain)): ## restrictive
+#            raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+#        self.domain = domain
+#
+#        if(target is None):
+#            target = domain.get_codomain()
+#        ## check codomain
+#        self.domain.check_codomain(target) ## a bit pointless
+#        self.target = target
+#
+#        ## check degrees of freedom
+#        if(self.domain.dof()>op.domain.dof()):
+#            about.infos.cprint("INFO: variant numbers of degrees of freedom ( "+str(self.domain.dof())+" / "+str(op.domain.dof())+" ).")
+#
+#        if(random not in ["pm1","gau"]):
+#            raise ValueError(about._errors.cstring("ERROR: unsupported random key '"+str(random)+"'."))
+#        self.random = random
+#
+#        self.ncpu = int(max(1,ncpu))
+#        self.nrun = int(max(self.ncpu**2,nrun))
+#        if(nper is None):
+#            self.nper = None
+#        else:
+#            self.nper = int(max(1,min(self.nrun//self.ncpu,nper)))
+#
+#        self.var = bool(var)
+#
+#        if(save):
+#            path = os.path.expanduser(str(path))
+#            if(not os.path.exists(path)):
+#                os.makedirs(path)
+#            self.save = os.path.join(path,str(prefix)) ## (back)slash inserted if needed
+#        else:
+#            self.save = None
+#
+#        self.quargs = quargs
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def configure(self,**kwargs):
+#        """
+#            changes the attributes of the instance
+#
+#            Parameters
+#            ----------
+#            random : string, *optional*
+#                the random number generator used to create the probes
+#                (default: "pm1")
+#            ncpu : int, *optional*
+#                the number of cpus to be used for parallel probing
+#                (default: 2)
+#            nrun : int, *optional*
+#                the number of probes to be evaluated. If `nrun<ncpu**2`, it will
+#                be set to `ncpu**2`. (default: 8)
+#            nper : int, *optional*
+#                number of probes, that will be evaluated by one worker
+#                (default: 8)
+#            var : bool, *optional*
+#                whether the variance will be additionally returned
+#                (default: False)
+#            save : bool, *optional*
+#                whether the individual probing results will be saved to the HDD
+#                (default: False)
+#            path : string, *optional*
+#                the path, where the probing results are saved (default: "tmp")
+#            prefix : string, *optional*
+#                a prefix for the saved probing results (default: "")
+#
+#        """
+#        if("random" in kwargs):
+#            if(kwargs.get("random") not in ["pm1","gau"]):
+#                raise ValueError(about._errors.cstring("ERROR: unsupported random key '"+str(kwargs.get("random"))+"'."))
+#            self.random = kwargs.get("random")
+#
+#        if("ncpu" in kwargs):
+#            self.ncpu = int(max(1,kwargs.get("ncpu")))
+#        if("nrun" in kwargs):
+#            self.nrun = int(max(self.ncpu**2,kwargs.get("nrun")))
+#        if("nper" in kwargs):
+#            if(kwargs.get("nper") is None):
+#                self.nper = None
+#            else:
+#                self.nper = int(max(1,min(self.nrun//self.ncpu,kwargs.get("nper"))))
+#
+#        if("var" in kwargs):
+#            self.var = bool(kwargs.get("var"))
+#
+#        if("save" in kwargs):
+#            if(kwargs.get("save")):
+#                if("path" in kwargs):
+#                    path = kwargs.get("path")
+#                else:
+#                    if(self.save is not None):
+#                        about.warnings.cprint("WARNING: save path set to default.")
+#                    path = "tmp"
+#                if("prefix" in kwargs):
+#                    prefix = kwargs.get("prefix")
+#                else:
+#                    if(self.save is not None):
+#                        about.warnings.cprint("WARNING: save prefix set to default.")
+#                    prefix = ""
+#                path = os.path.expanduser(str(path))
+#                if(not os.path.exists(path)):
+#                    os.makedirs(path)
+#                self.save = os.path.join(path,str(prefix)) ## (back)slash inserted if needed
+#            else:
+#                self.save = None
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def probing(self,idnum,probe):
+#
+#        """
+#            Computes a single probing result given one probe
+#
+#            Parameters
+#            ----------
+#            probe : field
+#                the field on which `function` will be applied
+#            idnum : int
+#                    the identification number of the probing
+#
+#            Returns
+#            -------
+#            result : ndarray
+#                    the result of `probe*(function(probe))`
+#        """
+#        f = self.function(probe,**self.quargs)
+#        if(f is None):
+#            return None
+#        else:
+#            if(self.save is None):
+#                return np.conjugate(probe.val)*f.val
+#            else:
+#                result = np.conjugate(probe.val)*f.val
+#                np.save(self.save+"%08u"%idnum,result)
+#                return idnum
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def evaluate(self,results):
+#        """
+#            evaluates the probing results
+#
+#            Parameters
+#            ----------
+#            results : list
+#                the list of ndarrays containing the results of the individual
+#                probings.
+#
+#            Returns
+#            -------
+#            final : ndarray
+#                    the final probing result. 1/N Sum[ probe_i*f(probe_i) ]
+#            var : ndarray
+#                    the variance of the final probing result.
+#                (N(N-1))^(-1) Sum[ (probe_i*f(probe_i) - final)^2 ]
+#                If the variance is returned, the return will be a tuple with
+#                final in the zeroth entry and var in the first entry.
+#
+#        """
+#        num = len(results)
+#        if(num==0):
+#            about.warnings.cprint("WARNING: probing failed.")
+#            return None
+#        elif(self.save is None):
+#            if(self.var):
+#                return np.mean(np.array(results),axis=0,dtype=None,out=None),np.var(np.array(results),axis=0,dtype=None,out=None,ddof=0)/(num-1)
+#            else:
+#                return np.mean(np.array(results),axis=0,dtype=None,out=None)
+#        else:
+#            final = np.copy(np.load(self.save+"%08u.npy"%results[0],mmap_mode=None))
+#            for ii in xrange(1,num):
+#                final += np.load(self.save+"%08u.npy"%results[ii],mmap_mode=None)
+#            if(self.var):
+#                var = np.zeros(self.domain.dim(split=True),dtype=self.domain.datatype,order='C')
+#                for ii in xrange(num):
+#                    var += (final-np.load(self.save+"%08u.npy"%results[ii],mmap_mode=None))**2
+#                return final/num,var/(num*(num-1))
+#            else:
+#                return final/num
+#
+#    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+#
+#    def __repr__(self):
+#        return "<nifty.diagonal_probing>"
+#
+###-----------------------------------------------------------------------------
+
+##-----------------------------------------------------------------------------
+
+class _share(object):
+
+    __init__ = None
+
+    @staticmethod
+    def _init_share(_sum,_num,_var):
+        _share.sum = _sum
+        _share.num = _num
+        _share.var = _var
+
+##-----------------------------------------------------------------------------
+
 ##=============================================================================
 
 class probing(object):
@@ -10337,7 +11475,7 @@ class probing(object):
             If on the other hand `nper=1`, then for each evaluation a worker will
             be created. In this case all cpus will work until nrun probes have
             been evaluated.
-            It is recommended to leave `nper` as the default value. (default: 8)
+            It is recommended to leave `nper` as the default value. (default: 1)
         var : bool, *optional*
             If `var` is True, then the variance of the sampled function will
             also be returned. The result is then a tuple with the mean in the
@@ -10370,9 +11508,11 @@ class probing(object):
         var : bool
             whether the variance will be additionally returned, when the
             instance is called
+        quargs : dict
+            Keyword arguments passed to `function` in each call.
 
     """
-    def __init__(self,op=None,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False):
+    def __init__(self,op=None,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=1,var=False,**quargs):
         """
         initializes a probing instance
 
@@ -10415,7 +11555,7 @@ class probing(object):
             If on the other hand `nper=1`, then for each evaluation a worker will
             be created. In this case all cpus will work until nrun probes have
             been evaluated.
-            It is recommended to leave `nper` as the default value. (default: 8)
+            It is recommended to leave `nper` as the default value. (default: 1)
         var : bool, *optional*
             If `var` is True, then the variance of the sampled function will
             also be returned. The result is then a tuple with the mean in the
@@ -10440,7 +11580,7 @@ class probing(object):
             ## check whether correctly bound
             if(op!=function.im_self):
                 raise NameError(about._errors.cstring("ERROR: invalid input."))
-            ## check given domain
+            ## check given shape and domain
             if(domain is None)or(not isinstance(domain,space)):
                 if(function in [op.inverse_times,op.adjoint_times]):
                     domain = op.target
@@ -10449,16 +11589,21 @@ class probing(object):
             else:
                 if(function in [op.inverse_times,op.adjoint_times]):
                     op.target.check_codomain(domain) ## a bit pointless
+                    if(target is None)or(not isinstance(target,space)):
+                        target = op.target
                 else:
                     op.domain.check_codomain(domain) ## a bit pointless
+                    if(target is None)or(not isinstance(target,space)):
+                        target = op.domain
 
         self.function = function
         self.domain = domain
 
+        ## check codomain
         if(target is None):
             target = domain.get_codomain()
-        ## check codomain
-        self.domain.check_codomain(target) ## a bit pointless
+        else:
+            self.domain.check_codomain(target) ## a bit pointless
         self.target = target
 
         if(random not in ["pm1","gau"]):
@@ -10474,6 +11619,8 @@ class probing(object):
 
         self.var = bool(var)
 
+        self.quargs = quargs
+
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
     def configure(self,**kwargs):
@@ -10490,7 +11637,7 @@ class probing(object):
                 the number of probes to be evaluated. If `nrun<ncpu**2`, it will be
                 set to `ncpu**2`. (default: 8)
             nper : int, *optional*
-                number of probes, that will be evaluated by one worker (default: 8)
+                number of probes, that will be evaluated by one worker (default: 1)
             var : bool, *optional*
                 whether the variance will be additionally returned (default: False)
 
@@ -10548,7 +11695,7 @@ class probing(object):
                 depends on the function.
 
         """
-        f = self.function(probe)
+        f = self.function(probe,**self.quargs)
         if(isinstance(f,field)):
             return f.val
         else:
@@ -10556,38 +11703,59 @@ class probing(object):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    def evaluate(self,results):
+    def evaluate(self,summa,num,var):
         """
-            evaluates the probing results
+            Evaluates the probing results.
 
             Parameters
             ----------
-            results : list
-                the list containing the results of the individual probings.
-                The type of the list elements depends on the function.
+            summa : numpy.array
+                Sum of all probing results.
+            num : int
+                Number of successful probings (not returning ``None``).
+            var : numpy.array
+                Sum of all squared probing results
 
             Returns
             -------
-            final : array-like
-                    the final probing result. 1/N Sum[ probing(probe_i) ]
+            final : numpy.array
+                The final probing result; 1/N Sum[ probing(probe_i) ]
             var : array-like
-                    the variance of the final probing result.
-                (N(N-1))^(-1) Sum[ ( probing(probe_i) - final)^2 ]
-                If the variance is returned, the return will be a tuple with
-                `final` in the zeroth entry and `var` in the first entry.
+                The variance of the final probing result;
+                1/(N(N-1)) Sum[( probing(probe_i) - final )^2];
+                if the variance is returned, the return will be a tuple of
+                (`final`,`var`).
 
         """
-        if(len(results)==0):
-            return None
-        elif(self.var):
-            return np.mean(np.array(results),axis=0,dtype=None,out=None),np.var(np.array(results),axis=0,dtype=None,out=None,ddof=0)/(len(results)-1)
+        if(num<self.nrun):
+            about.infos.cflush(" ( %u probe(s) failed, effectiveness == %.1f%% )\n"%(self.nrun-num,100*num/self.nrun))
+            if(num==0):
+                about.warnings.cprint("WARNING: probing failed.")
+                return None
+        else:
+            about.infos.cflush("\n")
+
+        if(summa.size==1):
+            summa = summa.flatten(order='C')[0]
+            var = var.flatten(order='C')[0]
+        if(np.iscomplexobj(summa))and(np.all(np.imag(summa)==0)):
+            summa = np.real(summa)
+
+        final = summa*(1/num)
+        if(self.var):
+            if(num==1):
+                about.warnings.cprint("WARNING: infinite variance.")
+                return final,None
+            else:
+                var = var*(1/(num*(num-1)))-np.real(np.conjugate(final)*final)*(1/(num-1))
+                return final,var
         else:
-            return np.mean(np.array(results),axis=0,dtype=None,out=None)
+            return final
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    def _progress(self,idnum): ## > prints progress status by in upto 10 dots
-        tenths = 1+(10*idnum//self.nrun)
+    def _progress(self,irun): ## > prints progress status by in upto 10 dots
+        tenths = 1+(10*irun//self.nrun)
         about.infos.cflush(("\b")*10+('.')*tenths+(' ')*(10-tenths))
 
     def _single_probing(self,zipped): ## > performs one probing operation
@@ -10595,65 +11763,105 @@ class probing(object):
         np.random.seed(zipped[0])
         probe = self.gen_probe()
         ## do the actual probing
-        self._progress(zipped[1])
         return self.probing(zipped[1],probe)
 
     def _serial_probing(self,zipped): ## > performs the probing operation serially
         try:
-            return self._single_probing(zipped)
+            result = np.array(self._single_probing(zipped)).flatten(order='C')
         except:
             ## kill pool
             os.kill()
+        else:
+            if(result is not None):
+                if(np.iscomplexobj(result)):
+                    _share.sum[0].acquire(block=True,timeout=None)
+                    _share.sum[0][:] += np.real(result)
+                    _share.sum[0].release()
+                    _share.sum[1].acquire(block=True,timeout=None)
+                    _share.sum[1][:] += np.imag(result)
+                    _share.sum[1].release()
+                else:
+                    _share.sum.acquire(block=True,timeout=None)
+                    _share.sum[:] += result
+                    _share.sum.release()
+                _share.num.acquire(block=True,timeout=None)
+                _share.num.value += 1
+                _share.num.release()
+                if(self.var):
+                    _share.var.acquire(block=True,timeout=None)
+                    _share.var[:] += np.real(np.conjugate(result)*result)
+                    _share.var.release()
+                self._progress(_share.num.value)
 
     def _parallel_probing(self): ## > performs the probing operations in parallel
         ## define random seed
         seed = np.random.randint(10**8,high=None,size=self.nrun)
+        ## get output shape
+        result = self.probing(0,field(self.domain,val=None,target=self.target))
+        if(np.isscalar(result))or(np.size(result)==1):
+            shape = np.ones(1,dtype=np.int,order='C')
+        else:
+            shape = np.array(np.array(result).shape)
+        ## define shared objects
+        if(np.iscomplexobj(result)):
+            _sum = (ma('d',np.zeros(np.prod(shape,axis=0,dtype=np.int,out=None),dtype=np.float64,order='C'),lock=True),ma('d',np.zeros(np.prod(shape,axis=0,dtype=np.int,out=None),dtype=np.float64,order='C'),lock=True)) ## tuple(real,imag)
+        else:
+            _sum = ma('d',np.zeros(np.prod(shape,axis=0,dtype=np.int,out=None),dtype=np.float64,order='C'),lock=True)
+        _num = mv('I',0,lock=True)
+        if(self.var):
+            _var = ma('d',np.zeros(np.prod(shape,axis=0,dtype=np.int,out=None),dtype=np.float64,order='C'),lock=True)
+        else:
+            _var = None
         ## build pool
         if(about.infos.status):
             so.write(about.infos.cstring("INFO: multiprocessing "+(' ')*10))
             so.flush()
-        pool = mp(processes=self.ncpu,initializer=None,initargs=(),maxtasksperchild=self.nper)
+        pool = mp(processes=self.ncpu,initializer=_share._init_share,initargs=(_sum,_num,_var),maxtasksperchild=self.nper)
         try:
-            ## retrieve results
-            results = pool.map_async(self._serial_probing,zip(seed,np.arange(self.nrun,dtype=np.int)),chunksize=None,callback=None).get(timeout=None)
-            ## close pool
+            ## pooling
+            pool.map_async(self._serial_probing,zip(seed,np.arange(self.nrun,dtype=np.int)),chunksize=None,callback=None).get(timeout=None)
+            ## close and join pool
             about.infos.cflush(" done.")
             pool.close()
+            pool.join()
         except:
-            ## terminate pool
+            ## terminate and join pool
             pool.terminate()
+            pool.join()
             raise Exception(about._errors.cstring("ERROR: unknown. NOTE: pool terminated.")) ## traceback by looping
-        ## cleanup
-        results = [rr for rr in results if(rr is not None)]
-        if(len(results)<self.nrun):
-            about.infos.cflush(" ( %u probe(s) failed, effectiveness == %.1f%% )\n"%(self.nrun-len(results),100*len(results)/self.nrun))
-        else:
-            about.infos.cflush("\n")
         ## evaluate
-        return self.evaluate(results)
+        if(np.iscomplexobj(result)):
+            _sum = (np.array(_sum[0][:])+np.array(_sum[1][:])*1j).reshape(shape) ## comlpex array
+        else:
+            _sum = np.array(_sum[:]).reshape(shape)
+        if(self.var):
+            _var = np.array(_var[:]).reshape(shape)
+        return self.evaluate(_sum,_num.value,_var)
 
     def _nonparallel_probing(self): ## > performs the probing operations one after another
-        ## define random seed
-        seed = np.random.randint(10**8,high=None,size=self.nrun)
-        ## retrieve results
+        ## looping
         if(about.infos.status):
             so.write(about.infos.cstring("INFO: looping "+(' ')*10))
             so.flush()
-        results = map(self._single_probing,zip(seed,np.arange(self.nrun,dtype=np.int)))
+        _sum = 0
+        _num = 0
+        _var = 0
+        for ii in xrange(self.nrun):
+            result = self._single_probing((np.random.randint(10**8,high=None,size=None),ii)) ## tuple(seed,idnum)
+            if(result is not None):
+                _sum += result ## result: {scalar, np.array}
+                _num += 1
+                if(self.var):
+                    _var += np.real(np.conjugate(result)*result)
+                self._progress(_num)
         about.infos.cflush(" done.")
-        ## cleanup
-        results = [rr for rr in results if(rr is not None)]
-        if(len(results)<self.nrun):
-            about.infos.cflush(" ( %u probe(s) failed, effectiveness == %.1f%% )\n"%(self.nrun-len(results),100*len(results)/self.nrun))
-        else:
-            about.infos.cflush("\n")
         ## evaluate
-        return self.evaluate(results)
+        return self.evaluate(_sum,_num,_var)
 
     def __call__(self,loop=False,**kwargs):
         """
 
-            starts the probing process.
+            Starts the probing process.
             All keyword arguments that can be given to `configure` can also be
             given to `__call__` and have the same effect.
 
@@ -10746,7 +11954,7 @@ class trace_probing(probing):
             If on the other hand `nper=1`, then for each evaluation a worker will
             be created. In this case all cpus will work until nrun probes have
             been evaluated.
-            It is recommended to leave `nper` as the default value. (default: 8)
+            It is recommended to leave `nper` as the default value. (default: 1)
         var : bool, *optional*
             If `var` is True, then the variance of the sampled function will
             also be returned. The result is then a tuple with the mean in the
@@ -10781,9 +11989,11 @@ class trace_probing(probing):
         var : bool
             whether the variance will be additionally returned, when the
             instance is called
+        quargs : dict
+            Keyword arguments passed to `function` in each call.
 
     """
-    def __init__(self,op,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False):
+    def __init__(self,op,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=1,var=False,**quargs):
         """
         initializes a trace probing instance
 
@@ -10826,7 +12036,7 @@ class trace_probing(probing):
             If on the other hand `nper=1`, then for each evaluation a worker will
             be created. In this case all cpus will work until nrun probes have
             been evaluated.
-            It is recommended to leave `nper` as the default value. (default: 8)
+            It is recommended to leave `nper` as the default value. (default: 1)
         var : bool, *optional*
             If `var` is True, then the variance of the sampled function will
             also be returned. The result is then a tuple with the mean in the
@@ -10854,14 +12064,24 @@ class trace_probing(probing):
                 domain = op.target
             else:
                 domain = op.domain
-        elif(not op.domain.check_codomain(domain))or(not op.target.check_codomain(domain)): ## restrictive
-            raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+        else:
+            if(function in [op.inverse_times,op.adjoint_times]):
+                if(not op.target.check_codomain(domain)): ## restrictive
+                    raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+                if(target is None)or(not isinstance(target,space)):
+                    target = op.target
+            else:
+                if(not op.domain.check_codomain(domain)): ## restrictive
+                    raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+                if(target is None)or(not isinstance(target,space)):
+                    target = op.domain
         self.domain = domain
 
+        ## check codomain
         if(target is None):
             target = domain.get_codomain()
-        ## check codomain
-        self.domain.check_codomain(target) ## a bit pointless
+        else:
+            self.domain.check_codomain(target) ## a bit pointless
         self.target = target
 
         ## check degrees of freedom
@@ -10881,6 +12101,8 @@ class trace_probing(probing):
 
         self.var = bool(var)
 
+        self.quargs = quargs
+
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
     def probing(self,idnum,probe):
@@ -10899,7 +12121,7 @@ class trace_probing(probing):
             result : float
                     the result of `probe.dot(function(probe))`
         """
-        f = self.function(probe)
+        f = self.function(probe,**self.quargs) ## field
         if(f is None):
             return None
         else:
@@ -10907,6 +12129,123 @@ class trace_probing(probing):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
+    def evaluate(self,summa,num,var):
+        """
+            Evaluates the probing results.
+
+            Parameters
+            ----------
+            summa : scalar
+                Sum of all probing results.
+            num : int
+                Number of successful probings (not returning ``None``).
+            var : scalar
+                Sum of all squared probing results
+
+            Returns
+            -------
+            final : scalar
+                The final probing result; 1/N Sum[ probing(probe_i) ]
+            var : scalar
+                The variance of the final probing result;
+                1/(N(N-1)) Sum[( probing(probe_i) - final )^2];
+                if the variance is returned, the return will be a tuple of
+                (`final`,`var`).
+
+        """
+        if(num<self.nrun):
+            about.infos.cflush(" ( %u probe(s) failed, effectiveness == %.1f%% )\n"%(self.nrun-num,100*num/self.nrun))
+            if(num==0):
+                about.warnings.cprint("WARNING: probing failed.")
+                return None
+        else:
+            about.infos.cflush("\n")
+
+        if(self.domain.datatype in [np.complex64,np.complex128]):
+            summa = np.real(summa)
+
+        final = summa/num
+        if(self.var):
+            if(num==1):
+                about.warnings.cprint("WARNING: infinite variance.")
+                return final,None
+            else:
+                var = var/(num*(num-1))-np.real(np.conjugate(final)*final)/(num-1)
+                return final,var
+        else:
+            return final
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _serial_probing(self,zipped): ## > performs the probing operation serially
+        try:
+            result = self._single_probing(zipped)
+        except:
+            ## kill pool
+            os.kill()
+        else:
+            if(result is not None):
+                if(np.iscomplexobj(result)):
+                    _share.sum[0].acquire(block=True,timeout=None)
+                    _share.sum[0].value += np.real(result)
+                    _share.sum[0].release()
+                    _share.sum[1].acquire(block=True,timeout=None)
+                    _share.sum[1].value += np.imag(result)
+                    _share.sum[1].release()
+                else:
+                    _share.sum.acquire(block=True,timeout=None)
+                    _share.sum.value += result
+                    _share.sum.release()
+                _share.num.acquire(block=True,timeout=None)
+                _share.num.value += 1
+                _share.num.release()
+                if(self.var):
+                    _share.var.acquire(block=True,timeout=None)
+                    _share.var.value += np.real(np.conjugate(result)*result)
+                    _share.var.release()
+                self._progress(_share.num.value)
+
+    def _parallel_probing(self): ## > performs the probing operations in parallel
+        ## define random seed
+        seed = np.random.randint(10**8,high=None,size=self.nrun)
+        ## define shared objects
+        if(self.domain.datatype in [np.complex64,np.complex128]):
+            _sum = (mv('d',0,lock=True),mv('d',0,lock=True)) ## tuple(real,imag)
+        else:
+            _sum = mv('d',0,lock=True)
+        _num = mv('I',0,lock=True)
+        if(self.var):
+            _var = mv('d',0,lock=True)
+        else:
+            _var = None
+        ## build pool
+        if(about.infos.status):
+            so.write(about.infos.cstring("INFO: multiprocessing "+(' ')*10))
+            so.flush()
+        pool = mp(processes=self.ncpu,initializer=_share._init_share,initargs=(_sum,_num,_var),maxtasksperchild=self.nper)
+        try:
+            ## pooling
+            pool.map_async(self._serial_probing,zip(seed,np.arange(self.nrun,dtype=np.int)),chunksize=None,callback=None).get(timeout=None)
+            ## close and join pool
+            about.infos.cflush(" done.")
+            pool.close()
+            pool.join()
+        except:
+            ## terminate and join pool
+            pool.terminate()
+            pool.join()
+            raise Exception(about._errors.cstring("ERROR: unknown. NOTE: pool terminated.")) ## traceback by looping
+        ## evaluate
+        if(self.domain.datatype in [np.complex64,np.complex128]):
+            _sum = np.complex(_sum[0].value,_sum[1].value)
+        else:
+            _sum = _sum.value
+        if(self.var):
+            _var = _var.value
+        return self.evaluate(_sum,_num.value,_var)
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
     def __repr__(self):
         return "<nifty.trace_probing>"
 
@@ -10975,7 +12314,7 @@ class diagonal_probing(probing):
             If on the other hand `nper=1`, then for each evaluation a worker will
             be created. In this case all cpus will work until nrun probes have
             been evaluated.
-            It is recommended to leave `nper` as the default value. (default: 8)
+            It is recommended to leave `nper` as the default value. (default: 1)
         var : bool, *optional*
             If `var` is True, then the variance of the sampled function will
             also be returned. The result is then a tuple with the mean in the
@@ -11027,9 +12366,11 @@ class diagonal_probing(probing):
         save : {string, None}
             the path and prefix for saved probe files. None in the case where
             the probing results are stored in the RAM.
+        quargs : dict
+            Keyword arguments passed to `function` in each call.
 
     """
-    def __init__(self,op,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=None,var=False,save=False,path="tmp",prefix=""):
+    def __init__(self,op,function=None,domain=None,target=None,random="pm1",ncpu=2,nrun=8,nper=1,var=False,save=False,path="tmp",prefix="",**quargs):
         """
         initializes a diagonal probing instance
 
@@ -11113,14 +12454,24 @@ class diagonal_probing(probing):
                 domain = op.target
             else:
                 domain = op.domain
-        elif(not op.domain.check_codomain(domain))or(not op.target.check_codomain(domain)): ## restrictive
-            raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+        else:
+            if(function in [op.inverse_times,op.adjoint_times]):
+                if(not op.target.check_codomain(domain)): ## restrictive
+                    raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+                if(target is None)or(not isinstance(target,space)):
+                    target = op.target
+            else:
+                if(not op.domain.check_codomain(domain)): ## restrictive
+                    raise ValueError(about._errors.cstring("ERROR: incompatible domains."))
+                if(target is None)or(not isinstance(target,space)):
+                    target = op.domain
         self.domain = domain
 
+        ## check codomain
         if(target is None):
             target = domain.get_codomain()
-        ## check codomain
-        self.domain.check_codomain(target) ## a bit pointless
+        else:
+            self.domain.check_codomain(target) ## a bit pointless
         self.target = target
 
         ## check degrees of freedom
@@ -11148,6 +12499,8 @@ class diagonal_probing(probing):
         else:
             self.save = None
 
+        self.quargs = quargs
+
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
     def configure(self,**kwargs):
@@ -11167,7 +12520,7 @@ class diagonal_probing(probing):
                 be set to `ncpu**2`. (default: 8)
             nper : int, *optional*
                 number of probes, that will be evaluated by one worker
-                (default: 8)
+                (default: 1)
             var : bool, *optional*
                 whether the variance will be additionally returned
                 (default: False)
@@ -11238,59 +12591,83 @@ class diagonal_probing(probing):
             result : ndarray
                     the result of `probe*(function(probe))`
         """
-        f = self.function(probe)
+        f = self.function(probe,**self.quargs) ## field
         if(f is None):
             return None
         else:
-            if(self.save is None):
-                return np.conjugate(probe.val)*f.val
-            else:
-                result = np.conjugate(probe.val)*f.val
+            result = np.conjugate(probe.val)*f.val
+            if(self.save is not None):
                 np.save(self.save+"%08u"%idnum,result)
-                return idnum
+            return result
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    def evaluate(self,results):
-        """
-            evaluates the probing results
-
-            Parameters
-            ----------
-            results : list
-                the list of ndarrays containing the results of the individual
-                probings.
-
-            Returns
-            -------
-            final : ndarray
-                    the final probing result. 1/N Sum[ probe_i*f(probe_i) ]
-            var : ndarray
-                    the variance of the final probing result.
-                (N(N-1))^(-1) Sum[ (probe_i*f(probe_i) - final)^2 ]
-                If the variance is returned, the return will be a tuple with
-                final in the zeroth entry and var in the first entry.
+    def _serial_probing(self,zipped): ## > performs the probing operation serially
+        try:
+            result = np.array(self._single_probing(zipped)).flatten(order='C')
+        except:
+            ## kill pool
+            os.kill()
+        else:
+            if(result is not None):
+                if(np.iscomplexobj(result)):
+                    _share.sum[0].acquire(block=True,timeout=None)
+                    _share.sum[0][:] += np.real(result)
+                    _share.sum[0].release()
+                    _share.sum[1].acquire(block=True,timeout=None)
+                    _share.sum[1][:] += np.imag(result)
+                    _share.sum[1].release()
+                else:
+                    _share.sum.acquire(block=True,timeout=None)
+                    _share.sum[:] += result
+                    _share.sum.release()
+                _share.num.acquire(block=True,timeout=None)
+                _share.num.value += 1
+                _share.num.release()
+                if(self.var):
+                    _share.var.acquire(block=True,timeout=None)
+                    _share.var[:] += np.real(np.conjugate(result)*result)
+                    _share.var.release()
+                self._progress(_share.num.value)
 
-        """
-        num = len(results)
-        if(num==0):
-            return None
-        elif(self.save is None):
-            if(self.var):
-                return np.mean(np.array(results),axis=0,dtype=None,out=None),np.var(np.array(results),axis=0,dtype=None,out=None,ddof=0)/(num-1)
-            else:
-                return np.mean(np.array(results),axis=0,dtype=None,out=None)
-        else:
-            final = np.copy(np.load(self.save+"%08u.npy"%results[0],mmap_mode=None))
-            for ii in xrange(1,num):
-                final += np.load(self.save+"%08u.npy"%results[ii],mmap_mode=None)
-            if(self.var):
-                var = np.zeros(self.domain.dim(split=True),dtype=self.domain.datatype,order='C')
-                for ii in xrange(num):
-                    var += (final-np.load(self.save+"%08u.npy"%results[ii],mmap_mode=None))**2
-                return final/num,var/(num*(num-1))
-            else:
-                return final/num
+    def _parallel_probing(self): ## > performs the probing operations in parallel
+        ## define random seed
+        seed = np.random.randint(10**8,high=None,size=self.nrun)
+        ## define shared objects
+        if(self.domain.datatype in [np.complex64,np.complex128]):
+            _sum = (ma('d',np.zeros(self.domain.dim(split=False),dtype=np.float64,order='C'),lock=True),ma('d',np.zeros(self.domain.dim(split=False),dtype=np.float64,order='C'),lock=True)) ## tuple(real,imag)
+        else:
+            _sum = ma('d',np.zeros(self.domain.dim(split=False),dtype=np.float64,order='C'),lock=True)
+        _num = mv('I',0,lock=True)
+        if(self.var):
+            _var = ma('d',np.zeros(self.domain.dim(split=False),dtype=np.float64,order='C'),lock=True)
+        else:
+            _var = None
+        ## build pool
+        if(about.infos.status):
+            so.write(about.infos.cstring("INFO: multiprocessing "+(' ')*10))
+            so.flush()
+        pool = mp(processes=self.ncpu,initializer=_share._init_share,initargs=(_sum,_num,_var),maxtasksperchild=self.nper)
+        try:
+            ## pooling
+            pool.map_async(self._serial_probing,zip(seed,np.arange(self.nrun,dtype=np.int)),chunksize=None,callback=None).get(timeout=None)
+            ## close and join pool
+            about.infos.cflush(" done.")
+            pool.close()
+            pool.join()
+        except:
+            ## terminate and join pool
+            pool.terminate()
+            pool.join()
+            raise Exception(about._errors.cstring("ERROR: unknown. NOTE: pool terminated.")) ## traceback by looping
+        ## evaluate
+        if(self.domain.datatype in [np.complex64,np.complex128]):
+            _sum = (np.array(_sum[0][:])+np.array(_sum[1][:])*1j).reshape(self.domain.dim(split=True)) ## comlpex array
+        else:
+            _sum = np.array(_sum[:]).reshape(self.domain.dim(split=True))
+        if(self.var):
+            _var = np.array(_var[:]).reshape(self.domain.dim(split=True))
+        return self.evaluate(_sum,_num.value,_var)
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
diff --git a/nifty_power.py b/nifty_power.py
index 2c50205b73ba0df87442bcc9357c5a4ca0935fb6..98758541ccda08163c60578ab3fcb5152638156e 100644
--- a/nifty_power.py
+++ b/nifty_power.py
@@ -28,11 +28,11 @@
     ..     /__/ /__/ /__/ /__/    \___/  \___   /  power
     ..                                  /______/
 
-    NIFTy offers a number of automatized routines for handling
+    NIFTY offers a number of automatized routines for handling
     power spectra. It is possible to draw a field from a random distribution
     with a certain autocorrelation or, equivalently, with a certain
     power spectrum in its conjugate space (see :py:func:`field.random`). In
-    NIFTy, it is usually assumed that such a field follows statistical
+    NIFTY, it is usually assumed that such a field follows statistical
     homogeneity and isotropy. Fields which are only statistically homogeneous
     can also be created using the diagonal operator routine.
 
@@ -43,7 +43,7 @@
 from __future__ import division
 from scipy.interpolate import interp1d as ip ## conflicts with sphinx's autodoc
 #import numpy as np
-from nifty.nifty_core import *
+from nifty_core import *
 import smoothing as gs
 
 
@@ -291,7 +291,7 @@ def _calc_inverse(tk,var,kindex,rho,b1,Amem): ## > computes the inverse Hessian
     ## inversion
     return np.linalg.inv(T2+np.diag(b2,k=0)),b2,Amem
 
-def infer_power(m,domain=None,Sk=None,D=None,pindex=None,pundex=None,kindex=None,rho=None,q=1E-42,alpha=1,perception=(1,0),smoothness=True,var=10,bare=True,**kwargs):
+def infer_power(m,domain=None,Sk=None,D=None,pindex=None,pundex=None,kindex=None,rho=None,q=1E-42,alpha=1,perception=(1,0),smoothness=True,var=10,force=False,bare=True,**kwargs):
     """
         Infers the power spectrum.
 
@@ -338,6 +338,9 @@ def infer_power(m,domain=None,Sk=None,D=None,pindex=None,pundex=None,kindex=None
             (default: True).
         var : {scalar, list, array}, *optional*
             Variance of the assumed spectral smoothness prior (default: 10).
+        force : bool, *optional*, *experimental*
+            Indicates whether smoothness is to be enforces or not
+            (default: False).
         bare : bool, *optional*
             Indicates whether the power spectrum entries returned are "bare"
             or not (mandatory for the correct incorporation of volume weights)
@@ -401,7 +404,7 @@ def infer_power(m,domain=None,Sk=None,D=None,pindex=None,pundex=None,kindex=None
         derived, and the implications of a certain choise of the perception
         tuple (delta,epsilon) are discussed.
         The further incorporation of a smoothness prior as detailed in [#]_,
-        where the underlying formula(s), Eq.(27), of this implementation are
+        where the underlying formula(s), Eq.(26), of this implementation are
         derived and discussed in terms of their applicability.
 
         References
@@ -505,6 +508,7 @@ def infer_power(m,domain=None,Sk=None,D=None,pindex=None,pundex=None,kindex=None
             numerator = weight_power(domain,numerator,power=-1,pindex=pindex,pundex=pundex) ## bare(!)
 
         ## smoothness prior
+        permill = 0
         divergence = 1
         while(divergence):
             pk = numerator/denominator1 ## bare(!)
@@ -524,7 +528,7 @@ def infer_power(m,domain=None,Sk=None,D=None,pindex=None,pundex=None,kindex=None
                     absdelta = np.abs(delta).max()
                     tk += min(1,0.1/absdelta)*delta # adaptive step width
                     pk *= np.exp(min(1,0.1/absdelta)*delta) # adaptive step width
-                var_ /= 1.1 # lowering the variance when converged
+                var_ /= 1.1+permill # lowering the variance when converged
                 if(var_<var):
                     if(breakinfo): # making sure there's one iteration with the correct variance
                         break
@@ -538,6 +542,14 @@ def infer_power(m,domain=None,Sk=None,D=None,pindex=None,pundex=None,kindex=None
                         break
                     else:
                         divergence += 1
+                        if(force):
+                            permill = 0.001
+                elif(force)and(var_/var_OLD>1.001):
+                        permill = 0
+                        pot = int(np.log10(var_))
+                        var = int(1+var_*10**-pot)*10**pot
+                        about.warnings.cprint("WARNING: smoothness variance increased ( var = "+str(var)+" ).")
+                        break
                 else:
                     var_OLD = var_
             if(breakinfo):
diff --git a/nifty_tools.py b/nifty_tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..9732744515f4710e8fe5237743d593e7777dcf50
--- /dev/null
+++ b/nifty_tools.py
@@ -0,0 +1,1136 @@
+## NIFTY (Numerical Information Field Theory) has been developed at the
+## Max-Planck-Institute for Astrophysics.
+##
+## Copyright (C) 2013 Max-Planck-Society
+##
+## Author: Marco Selig
+## Project homepage: <http://www.mpa-garching.mpg.de/ift/nifty/>
+##
+## 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/>.
+
+"""
+    ..                     __   ____   __
+    ..                   /__/ /   _/ /  /_
+    ..         __ ___    __  /  /_  /   _/  __   __
+    ..       /   _   | /  / /   _/ /  /   /  / /  /
+    ..      /  / /  / /  / /  /   /  /_  /  /_/  /
+    ..     /__/ /__/ /__/ /__/    \___/  \___   /  tools
+    ..                                  /______/
+
+    This module extends NIFTY with a nifty set of tools including further
+    operators, namely the :py:class:`invertible_operator` and the
+    :py:class:`propagator_operator`, and minimization schemes, namely
+    :py:class:`steepest_descent` and :py:class:`conjugate_gradient`. Those
+    tools are supposed to support the user in solving information field
+    theoretical problems (almost) without numerical pain.
+
+"""
+from __future__ import division
+#import numpy as np
+from nifty_core import *
+
+
+##-----------------------------------------------------------------------------
+
+class invertible_operator(operator):
+    """
+        ..       __                                       __     __   __        __
+        ..     /__/                                     /  /_  /__/ /  /      /  /
+        ..     __   __ ___  __   __   _______   _____  /   _/  __  /  /___   /  /   _______
+        ..   /  / /   _   ||  |/  / /   __  / /   __/ /  /   /  / /   _   | /  /  /   __  /
+        ..  /  / /  / /  / |     / /  /____/ /  /    /  /_  /  / /  /_/  / /  /_ /  /____/
+        .. /__/ /__/ /__/  |____/  \______/ /__/     \___/ /__/  \______/  \___/ \______/  operator class
+
+        NIFTY subclass for invertible, self-adjoint (linear) operators
+
+        The invertible operator class is an abstract class for self-adjoint or
+        symmetric (linear) operators from which other more specific operator
+        subclassescan be derived. Such operators inherit an automated inversion
+        routine, namely conjugate gradient.
+
+        Parameters
+        ----------
+        domain : space
+            The space wherein valid arguments live.
+        uni : bool, *optional*
+            Indicates whether the operator is unitary or not.
+            (default: False)
+        imp : bool, *optional*
+            Indicates whether the incorporation of volume weights in
+            multiplications is already implemented in the `multiply`
+            instance methods or not (default: False).
+        para : {single object, tuple/list of objects}, *optional*
+            This is a freeform tuple/list of parameters that derivatives of
+            the operator class can use (default: None).
+
+        See Also
+        --------
+        operator
+
+        Notes
+        -----
+        This class is not meant to be instantiated. Operator classes derived
+        from this one only need a `_multiply` or `_inverse_multiply` instance
+        method to perform the other. However, one of them needs to be defined.
+
+        Attributes
+        ----------
+        domain : space
+            The space wherein valid arguments live.
+        sym : bool
+            Indicates whether the operator is self-adjoint or not.
+        uni : bool
+            Indicates whether the operator is unitary or not.
+        imp : bool
+            Indicates whether the incorporation of volume weights in
+            multiplications is already implemented in the `multiply`
+            instance methods or not.
+        target : space
+            The space wherein the operator output lives.
+        para : {single object, list of objects}
+            This is a freeform tuple/list of parameters that derivatives of
+            the operator class can use. Not used in the base operators.
+
+    """
+    def __init__(self,domain,uni=False,imp=False,para=None):
+        """
+            Sets the standard operator properties.
+
+            Parameters
+            ----------
+            domain : space
+                The space wherein valid arguments live.
+            uni : bool, *optional*
+                Indicates whether the operator is unitary or not.
+                (default: False)
+            imp : bool, *optional*
+                Indicates whether the incorporation of volume weights in
+                multiplications is already implemented in the `multiply`
+                instance methods or not (default: False).
+            para : {single object, tuple/list of objects}, *optional*
+                This is a freeform tuple/list of parameters that derivatives of
+                the operator class can use (default: None).
+
+        """
+        if(not isinstance(domain,space)):
+            raise TypeError(about._errors.cstring("ERROR: invalid input."))
+        self.domain = domain
+        self.sym = True
+        self.uni = bool(uni)
+
+        if(self.domain.discrete):
+            self.imp = True
+        else:
+            self.imp = bool(imp)
+
+        self.target = self.domain
+
+        if(para is not None):
+            self.para = para
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _multiply(self,x,force=False,W=None,spam=None,reset=None,note=False,x0=None,tol=1E-4,clevel=1,limii=None,**kwargs):
+        """
+            Applies the invertible operator to a given field by invoking a
+            conjugate gradient.
+
+            Parameters
+            ----------
+            x : field
+                Valid input field.
+            force : bool
+                Indicates wheter to return a field instead of ``None`` is
+                forced incase the conjugate gradient fails.
+
+            Returns
+            -------
+            OIIx : field
+                Mapped field with suitable domain.
+
+            See Also
+            --------
+            conjugate_gradient
+
+            Other Parameters
+            ----------------
+            W : {operator, function}, *optional*
+                Operator `W` that is a preconditioner on `A` and is applicable to a
+                field (default: None).
+            spam : function, *optional*
+                Callback function which is given the current `x` and iteration
+                counter each iteration (default: None).
+            reset : integer, *optional*
+                Number of iterations after which to restart; i.e., forget previous
+                conjugated directions (default: sqrt(b.dim())).
+            note : bool, *optional*
+                Indicates whether notes are printed or not (default: False).
+            x0 : field, *optional*
+                Starting guess for the minimization.
+            tol : scalar, *optional*
+                Tolerance specifying convergence; measured by current relative
+                residual (default: 1E-4).
+            clevel : integer, *optional*
+                Number of times the tolerance should be undershot before
+                exiting (default: 1).
+            limii : integer, *optional*
+                Maximum number of iterations performed (default: 10 * b.dim()).
+
+        """
+        x_,convergence = conjugate_gradient(self.inverse_times,x,W=W,spam=spam,reset=reset,note=note)(x0=x0,tol=tol,clevel=clevel,limii=limii)
+        ## check convergence
+        if(not convergence):
+            if(not force)or(x_ is None):
+                return None
+            about.warnings.cprint("WARNING: conjugate gradient failed.")
+        ## weight if ...
+        if(not self.imp): ## continiuos domain/target
+            x_.weight(power=-1,overwrite=True)
+        return x_
+
+    def _inverse_multiply(self,x,force=False,W=None,spam=None,reset=None,note=False,x0=None,tol=1E-4,clevel=1,limii=None,**kwargs):
+        """
+            Applies the inverse of the invertible operator to a given field by
+            invoking a conjugate gradient.
+
+            Parameters
+            ----------
+            x : field
+                Valid input field.
+            force : bool
+                Indicates wheter to return a field instead of ``None`` is
+                forced incase the conjugate gradient fails.
+
+            Returns
+            -------
+            OIx : field
+                Mapped field with suitable domain.
+
+            See Also
+            --------
+            conjugate_gradient
+
+            Other Parameters
+            ----------------
+            W : {operator, function}, *optional*
+                Operator `W` that is a preconditioner on `A` and is applicable to a
+                field (default: None).
+            spam : function, *optional*
+                Callback function which is given the current `x` and iteration
+                counter each iteration (default: None).
+            reset : integer, *optional*
+                Number of iterations after which to restart; i.e., forget previous
+                conjugated directions (default: sqrt(b.dim())).
+            note : bool, *optional*
+                Indicates whether notes are printed or not (default: False).
+            x0 : field, *optional*
+                Starting guess for the minimization.
+            tol : scalar, *optional*
+                Tolerance specifying convergence; measured by current relative
+                residual (default: 1E-4).
+            clevel : integer, *optional*
+                Number of times the tolerance should be undershot before
+                exiting (default: 1).
+            limii : integer, *optional*
+                Maximum number of iterations performed (default: 10 * b.dim()).
+
+        """
+        x_,convergence = conjugate_gradient(self.times,x,W=W,spam=spam,reset=reset,note=note)(x0=x0,tol=tol,clevel=clevel,limii=limii)
+        ## check convergence
+        if(not convergence):
+            if(not force)or(x_ is None):
+                return None
+            about.warnings.cprint("WARNING: conjugate gradient failed.")
+        ## weight if ...
+        if(not self.imp): ## continiuos domain/target
+            x_.weight(power=1,overwrite=True)
+        return x_
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def __repr__(self):
+        return "<nifty.invertible_operator>"
+
+##-----------------------------------------------------------------------------
+
+##-----------------------------------------------------------------------------
+
+class propagator_operator(operator):
+    """
+        ..                                                                            __
+        ..                                                                          /  /_
+        ..      _______   _____   ______    ______    ____ __   ____ __   ____ __  /   _/  ______    _____
+        ..    /   _   / /   __/ /   _   | /   _   | /   _   / /   _   / /   _   / /  /   /   _   | /   __/
+        ..   /  /_/  / /  /    /  /_/  / /  /_/  / /  /_/  / /  /_/  / /  /_/  / /  /_  /  /_/  / /  /
+        ..  /   ____/ /__/     \______/ /   ____/  \______|  \___   /  \______|  \___/  \______/ /__/     operator class
+        .. /__/                        /__/                 /______/
+
+        NIFTY subclass for propagator operators (of a certain family)
+
+        The propagator operators :math:`D` implemented here have an inverse
+        formulation like :math:`(S^{-1} + M)`, :math:`(S^{-1} + N^{-1})`, or
+        :math:`(S^{-1} + R^\dagger N^{-1} R)` as appearing in Wiener filter
+        theory.
+
+        Parameters
+        ----------
+        S : operator
+            Covariance of the signal prior.
+        M : operator
+            Likelihood contribution.
+        R : operator
+            Response operator translating signal to (noiseless) data.
+        N : operator
+            Covariance of the noise prior or the likelihood, respectively.
+
+        See Also
+        --------
+        conjugate_gradient
+
+        Notes
+        -----
+        The propagator will puzzle the operators `S` and `M` or `R`, `N` or
+        only `N` together in the predefined from, a domain is set
+        automatically. The application of the inverse is done by invoking a
+        conjugate gradient.
+        Note that changes to `S`, `M`, `R` or `N` auto-update the propagator.
+
+        Examples
+        --------
+        >>> f = field(rg_space(4), val=[2, 4, 6, 8])
+        >>> S = power_operator(f.target, spec=1)
+        >>> N = diagonal_operator(f.domain, diag=1)
+        >>> D = propagator_operator(S=S, N=N) # D^{-1} = S^{-1} + N^{-1}
+        >>> D(f).val
+        array([ 1.,  2.,  3.,  4.])
+
+        Attributes
+        ----------
+        domain : space
+            A space wherein valid arguments live.
+        codomain : space
+            An alternative space wherein valid arguments live; commonly the
+            codomain of the `domain` attribute.
+        sym : bool
+            Indicates that the operator is self-adjoint.
+        uni : bool
+            Indicates that the operator is not unitary.
+        imp : bool
+            Indicates that volume weights are implemented in the `multiply`
+            instance methods.
+        target : space
+            The space wherein the operator output lives.
+        _A1 : {operator, function}
+            Application of :math:`S^{-1}` to a field.
+        _A2 : {operator, function}
+            Application of all operations not included in `A1` to a field.
+        RN : {2-tuple of operators}, *optional*
+            Contains `R` and `N` if given.
+
+    """
+    def __init__(self,S=None,M=None,R=None,N=None):
+        """
+            Sets the standard operator properties and `codomain`, `_A1`, `_A2`,
+            and `RN` if required.
+
+            Parameters
+            ----------
+            S : operator
+                Covariance of the signal prior.
+            M : operator
+                Likelihood contribution.
+            R : operator
+                Response operator translating signal to (noiseless) data.
+            N : operator
+                Covariance of the noise prior or the likelihood, respectively.
+
+        """
+        ## check signal prior covariance
+        if(S is None):
+            raise Exception(about._errors.cstring("ERROR: insufficient input."))
+        elif(not isinstance(S,operator)):
+            raise ValueError(about._errors.cstring("ERROR: invalid input."))
+        space1 = S.domain
+
+        ## check likelihood (pseudo) covariance
+        if(M is None):
+            if(N is None):
+                raise Exception(about._errors.cstring("ERROR: insufficient input."))
+            elif(not isinstance(N,operator)):
+                raise ValueError(about._errors.cstring("ERROR: invalid input."))
+            if(R is None):
+                space2 = N.domain
+            elif(not isinstance(R,operator)):
+                raise ValueError(about._errors.cstring("ERROR: invalid input."))
+            else:
+                space2 = R.domain
+        elif(not isinstance(M,operator)):
+            raise ValueError(about._errors.cstring("ERROR: invalid input."))
+        else:
+            space2 = M.domain
+
+        ## set spaces
+        self.domain = space2
+        if(self.domain.check_codomain(space1)):
+            self.codomain = space1
+        else:
+            raise ValueError(about._errors.cstring("ERROR: invalid input."))
+        self.target = self.domain
+
+        ## define A1 == S_inverse
+        if(isinstance(S,diagonal_operator)):
+            self._A1 = S._inverse_multiply ## S.imp == True
+        else:
+            self._A1 = S.inverse_times
+
+        ## define A2 == M == R_adjoint N_inverse R == N_inverse
+        if(M is None):
+            if(R is not None):
+                self.RN = (R,N)
+                if(isinstance(N,diagonal_operator)):
+                    self._A2 = self._standard_M_times_1
+                else:
+                    self._A2 = self._standard_M_times_2
+            elif(isinstance(N,diagonal_operator)):
+                self._A2 = N._inverse_multiply ## N.imp == True
+            else:
+                self._A2 = N.inverse_times
+        elif(isinstance(M,diagonal_operator)):
+            self._A2 = M._multiply ## M.imp == True
+        else:
+            self._A2 = M.times
+
+        self.sym = True
+        self.uni = False
+        self.imp = True
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _standard_M_times_1(self,x): ## applies > R_adjoint N_inverse R assuming N is diagonal
+        return self.RN[0].adjoint_times(self.RN[1]._inverse_multiply(self.RN[0].times(x))) ## N.imp = True
+
+    def _standard_M_times_2(self,x): ## applies > R_adjoint N_inverse R
+        return self.RN[0].adjoint_times(self.RN[1].inverse_times(self.RN[0].times(x)))
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _inverse_multiply_1(self,x,**kwargs): ## > applies A1 + A2 in self.codomain
+        return self._A1(x)+self._A2(x.transform(self.domain)).transform(self.codomain)
+
+    def _inverse_multiply_2(self,x,**kwargs): ## > applies A1 + A2 in self.domain
+        return self._A1(x.transform(self.codomain)).transform(self.domain)+self._A2(x)
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _briefing(self,x): ## > prepares x for `multiply`
+        ## inspect x
+        if(not isinstance(x,field)):
+            return field(self.domain,val=x,target=None),False
+        ## check x.domain
+        elif(x.domain==self.domain):
+            return x,False
+        elif(x.domain==self.codomain):
+            return x,True
+        ## transform
+        else:
+            return x.transform(target=self.codomain,overwrite=False),True
+
+    def _debriefing(self,x,x_,in_codomain): ## > evaluates x and x_ after `multiply`
+        if(x_ is None):
+            return None
+        ## inspect x
+        elif(isinstance(x,field)):
+            ## repair ...
+            if(in_codomain)and(x.domain!=self.codomain):
+                    x_ = x_.transform(target=x.domain,overwrite=False) ## ... domain
+            if(x_.target!=x.target):
+                x_.set_target(newtarget=x.target) ## ... codomain
+        return x_
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def times(self,x,force=False,W=None,spam=None,reset=None,note=False,x0=None,tol=1E-4,clevel=1,limii=None,**kwargs):
+        """
+            Applies the propagator to a given object by invoking a
+            conjugate gradient.
+
+            Parameters
+            ----------
+            x : {scalar, list, array, field}
+                Scalars are interpreted as constant arrays, and an array will
+                be interpreted as a field on the domain of the operator.
+            force : bool
+                Indicates wheter to return a field instead of ``None`` is
+                forced incase the conjugate gradient fails.
+
+            Returns
+            -------
+            Dx : field
+                Mapped field with suitable domain.
+
+            See Also
+            --------
+            conjugate_gradient
+
+            Other Parameters
+            ----------------
+            W : {operator, function}, *optional*
+                Operator `W` that is a preconditioner on `A` and is applicable to a
+                field (default: None).
+            spam : function, *optional*
+                Callback function which is given the current `x` and iteration
+                counter each iteration (default: None).
+            reset : integer, *optional*
+                Number of iterations after which to restart; i.e., forget previous
+                conjugated directions (default: sqrt(b.dim())).
+            note : bool, *optional*
+                Indicates whether notes are printed or not (default: False).
+            x0 : field, *optional*
+                Starting guess for the minimization.
+            tol : scalar, *optional*
+                Tolerance specifying convergence; measured by current relative
+                residual (default: 1E-4).
+            clevel : integer, *optional*
+                Number of times the tolerance should be undershot before
+                exiting (default: 1).
+            limii : integer, *optional*
+                Maximum number of iterations performed (default: 10 * b.dim()).
+
+        """
+        ## prepare
+        x_,in_codomain = self._briefing(x)
+        ## apply operator
+        if(in_codomain):
+            A = self._inverse_multiply_1
+        else:
+            A = self._inverse_multiply_2
+        x_,convergence = conjugate_gradient(A,x_,W=W,spam=spam,reset=reset,note=note)(x0=x0,tol=tol,clevel=clevel,limii=limii)
+        ## evaluate
+        if(not convergence):
+            if(not force):
+                return None
+            about.warnings.cprint("WARNING: conjugate gradient failed.")
+        return self._debriefing(x,x_,in_codomain)
+
+    def inverse_times(self,x,**kwargs):
+        """
+            Applies the inverse propagator to a given object.
+
+            Parameters
+            ----------
+            x : {scalar, list, array, field}
+                Scalars are interpreted as constant arrays, and an array will
+                be interpreted as a field on the domain of the operator.
+
+            Returns
+            -------
+            DIx : field
+                Mapped field with suitable domain.
+
+        """
+        ## prepare
+        x_,in_codomain = self._briefing(x)
+        ## apply operator
+        if(in_codomain):
+            x_ = self._inverse_multiply_1(x_)
+        else:
+            x_ = self._inverse_multiply_2(x_)
+        ## evaluate
+        return self._debriefing(x,x_,in_codomain)
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def __repr__(self):
+        return "<nifty.propagator_operator>"
+
+##-----------------------------------------------------------------------------
+
+##=============================================================================
+
+class conjugate_gradient(object):
+    """
+        ..      _______       ____ __
+        ..    /  _____/     /   _   /
+        ..   /  /____  __  /  /_/  / __
+        ..   \______//__/  \____  //__/  class
+        ..                /______/
+
+        NIFTY tool class for conjugate gradient
+
+        This tool minimizes :math:`A x = b` with respect to `x` given `A` and
+        `b` using a conjugate gradient; i.e., a step-by-step minimization
+        relying on conjugated gradient directions. Further, `A` is assumed to
+        be a positive definite and self-adjoint operator. The use of a
+        preconditioner `W` that is roughly the inverse of `A` is optional.
+        For details on the methodology refer to [#]_, for details on usage and
+        output, see the notes below.
+
+        Parameters
+        ----------
+        A : {operator, function}
+            Operator `A` applicable to a field.
+        b : field
+            Resulting field of the operation `A(x)`.
+        W : {operator, function}, *optional*
+            Operator `W` that is a preconditioner on `A` and is applicable to a
+            field (default: None).
+        spam : function, *optional*
+            Callback function which is given the current `x` and iteration
+            counter each iteration (default: None).
+        reset : integer, *optional*
+            Number of iterations after which to restart; i.e., forget previous
+            conjugated directions (default: sqrt(b.dim())).
+        note : bool, *optional*
+            Indicates whether notes are printed or not (default: False).
+
+        See Also
+        --------
+        scipy.sparse.linalg.cg
+
+        Notes
+        -----
+        After initialization by `__init__`, the minimizer is started by calling
+        it using `__call__`, which takes additional parameters. Notifications,
+        if enabled, will state the iteration number, current step widths
+        `alpha` and `beta`, the current relative residual `delta` that is
+        compared to the tolerance, and the convergence level if changed.
+        The minimizer will exit in three states: DEAD if alpha becomes
+        infinite, QUIT if the maximum number of iterations is reached, or DONE
+        if convergence is achieved. Returned will be the latest `x` and the
+        latest convergence level, which can evaluate ``True`` for the exit
+        states QUIT and DONE.
+
+        References
+        ----------
+        .. [#] J. R. Shewchuk, 1994, `"An Introduction to the Conjugate
+            Gradient Method Without the Agonizing Pain"
+            <http://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf>`_
+
+        Examples
+        --------
+        >>> b = field(point_space(2), val=[1, 9])
+        >>> A = diagonal_operator(b.domain, diag=[4, 3])
+        >>> x,convergence = conjugate_gradient(A, b, note=True)(tol=1E-4, clevel=3)
+        iteration : 00000001   alpha = 3.3E-01   beta = 1.3E-03   delta = 3.6E-02
+        iteration : 00000002   alpha = 2.5E-01   beta = 7.6E-04   delta = 1.0E-03
+        iteration : 00000003   alpha = 3.3E-01   beta = 2.5E-04   delta = 1.6E-05   convergence level : 1
+        iteration : 00000004   alpha = 2.5E-01   beta = 1.8E-06   delta = 2.1E-08   convergence level : 2
+        iteration : 00000005   alpha = 2.5E-01   beta = 2.2E-03   delta = 1.0E-09   convergence level : 3
+        ... done.
+        >>> bool(convergence)
+        True
+        >>> x.val # yields 1/4 and 9/3
+        array([ 0.25,  3.  ])
+
+        Attributes
+        ----------
+        A : {operator, function}
+            Operator `A` applicable to a field.
+        x : field
+            Current field.
+        b : field
+            Resulting field of the operation `A(x)`.
+        W : {operator, function}
+            Operator `W` that is a preconditioner on `A` and is applicable to a
+            field; can be ``None``.
+        spam : function
+            Callback function which is given the current `x` and iteration
+            counter each iteration; can be ``None``.
+        reset : integer
+            Number of iterations after which to restart; i.e., forget previous
+            conjugated directions (default: sqrt(b.dim())).
+        note : notification
+            Notification instance.
+
+    """
+    def __init__(self,A,b,W=None,spam=None,reset=None,note=False):
+        """
+            Initializes the conjugate_gradient and sets the attributes (except
+            for `x`).
+
+            Parameters
+            ----------
+            A : {operator, function}
+                Operator `A` applicable to a field.
+            b : field
+                Resulting field of the operation `A(x)`.
+            W : {operator, function}, *optional*
+                Operator `W` that is a preconditioner on `A` and is applicable to a
+                field (default: None).
+            spam : function, *optional*
+                Callback function which is given the current `x` and iteration
+                counter each iteration (default: None).
+            reset : integer, *optional*
+                Number of iterations after which to restart; i.e., forget previous
+                conjugated directions (default: sqrt(b.dim())).
+            note : bool, *optional*
+                Indicates whether notes are printed or not (default: False).
+
+        """
+        if(hasattr(A,"__call__")):
+            self.A = A ## applies A
+        else:
+            raise AttributeError(about._errors.cstring("ERROR: invalid input."))
+        self.b = b
+
+        if(W is None)or(hasattr(W,"__call__")):
+            self.W = W ## applies W ~ A_inverse
+        else:
+            raise AttributeError(about._errors.cstring("ERROR: invalid input."))
+
+        self.spam = spam ## serves as callback given x and iteration number
+        if(reset is None): ## 2 < reset ~ sqrt(dim)
+            self.reset = max(2,int(np.sqrt(b.domain.dim(split=False))))
+        else:
+            self.reset = max(2,int(reset))
+        self.note = notification(default=bool(note))
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def __call__(self,x0=None,**kwargs): ## > runs cg with/without preconditioner
+        """
+            Runs the conjugate gradient minimization.
+
+            Parameters
+            ----------
+            x0 : field, *optional*
+                Starting guess for the minimization.
+            tol : scalar, *optional*
+                Tolerance specifying convergence; measured by current relative
+                residual (default: 1E-4).
+            clevel : integer, *optional*
+                Number of times the tolerance should be undershot before
+                exiting (default: 1).
+            limii : integer, *optional*
+                Maximum number of iterations performed (default: 10 * b.dim()).
+
+            Returns
+            -------
+            x : field
+                Latest `x` of the minimization.
+            convergence : integer
+                Latest convergence level indicating whether the minimization
+                has converged or not.
+
+        """
+        self.x = field(self.b.domain,val=x0,target=self.b.target)
+
+        if(self.W is None):
+            return self._calc_without(**kwargs)
+        else:
+            return self._calc_with(**kwargs)
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _calc_without(self,tol=1E-4,clevel=1,limii=None): ## > runs cg without preconditioner
+        clevel = int(clevel)
+        if(limii is None):
+            limii = 10*self.b.domain.dim(split=False)
+        else:
+            limii = int(limii)
+
+        r = self.b-self.A(self.x)
+        d = field(self.b.domain,val=np.copy(r.val),target=self.b.target)
+        gamma = r.dot(d)
+        if(gamma==0):
+            return self.x,clevel+1
+        delta_ = np.absolute(gamma)**(-0.5)
+
+        convergence = 0
+        ii = 1
+        while(True):
+            q = self.A(d)
+            alpha = gamma/d.dot(q) ## positive definite
+            if(not np.isfinite(alpha)):
+                self.note.cprint("\niteration : %08u   alpha = NAN\n... dead."%ii)
+                return self.x,0
+            self.x += alpha*d
+            if(np.signbit(np.real(alpha))):
+                about.warnings.cprint("WARNING: positive definiteness of A violated.")
+                r = self.b-self.A(self.x)
+            elif(ii%self.reset==0):
+                r = self.b-self.A(self.x)
+            else:
+                r -= alpha*q
+            gamma_ = gamma
+            gamma = r.dot(r)
+            beta = max(0,gamma/gamma_) ## positive definite
+            d = r+beta*d
+
+            delta = delta_*np.absolute(gamma)**0.5
+            self.note.cflush("\niteration : %08u   alpha = %3.1E   beta = %3.1E   delta = %3.1E"%(ii,np.real(alpha),np.real(beta),np.real(delta)))
+            if(ii==limii):
+                self.note.cprint("\n... quit.")
+                break
+            elif(gamma==0):
+                convergence = clevel+1
+                self.note.cprint("   convergence level : INF\n... done.")
+                break
+            elif(np.absolute(delta)<tol):
+                convergence += 1
+                self.note.cflush("   convergence level : %u"%convergence)
+                if(convergence==clevel):
+                    self.note.cprint("\n... done.")
+                    break
+            else:
+                convergence = max(0,convergence-1)
+
+            if(self.spam is not None):
+                self.spam(self.x,ii)
+
+            ii += 1
+
+        if(self.spam is not None):
+            self.spam(self.x,ii)
+
+        return self.x,convergence
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _calc_with(self,tol=1E-4,clevel=1,limii=None): ## > runs cg with preconditioner
+
+        clevel = int(clevel)
+        if(limii is None):
+            limii = 10*self.b.domain.dim(split=False)
+        else:
+            limii = int(limii)
+
+        r = self.b-self.A(self.x)
+        d = self.W(r)
+        gamma = r.dot(d)
+        if(gamma==0):
+            return self.x,clevel+1
+        delta_ = np.absolute(gamma)**(-0.5)
+
+        convergence = 0
+        ii = 1
+        while(True):
+            q = self.A(d)
+            alpha = gamma/d.dot(q) ## positive definite
+            if(not np.isfinite(alpha)):
+                self.note.cprint("\niteration : %08u   alpha = NAN\n... dead."%ii)
+                return self.x,0
+            self.x += alpha*d ## update
+            if(np.signbit(np.real(alpha))):
+                about.warnings.cprint("WARNING: positive definiteness of A violated.")
+                r = self.b-self.A(self.x)
+            elif(ii%self.reset==0):
+                r = self.b-self.A(self.x)
+            else:
+                r -= alpha*q
+            s = self.W(r)
+            gamma_ = gamma
+            gamma = r.dot(s)
+            beta = max(0,gamma/gamma_) ## positive definite
+            d = s+beta*d ## conjugated gradient
+
+            delta = delta_*np.absolute(gamma)**0.5
+            self.note.cflush("\niteration : %08u   alpha = %3.1E   beta = %3.1E   delta = %3.1E"%(ii,np.real(alpha),np.real(beta),np.real(delta)))
+            if(ii==limii):
+                self.note.cprint("\n... quit.")
+                break
+            elif(gamma==0):
+                convergence = clevel+1
+                self.note.cprint("   convergence level : INF\n... done.")
+                break
+            elif(np.absolute(delta)<tol):
+                convergence += 1
+                self.note.cflush("   convergence level : %u"%convergence)
+                if(convergence==clevel):
+                    self.note.cprint("\n... done.")
+                    break
+            else:
+                convergence = max(0,convergence-1)
+
+            if(self.spam is not None):
+                self.spam(self.x,ii)
+
+            ii += 1
+
+        if(self.spam is not None):
+            self.spam(self.x,ii)
+
+        return self.x,convergence
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def __repr__(self):
+        return "<nifty.conjugate_gradient>"
+
+##=============================================================================
+
+
+
+
+
+##=============================================================================
+
+class steepest_descent(object):
+    """
+        ..                          __
+        ..                        /  /
+        ..      _______      ____/  /
+        ..    /  _____/    /   _   /
+        ..   /_____  / __ /  /_/  / __
+        ..  /_______//__/ \______|/__/  class
+
+        NIFTY tool class for steepest descent minimization
+
+        This tool minimizes a scalar energy-function by steepest descent using
+        the functions gradient. Steps and step widths are choosen according to
+        the Wolfe conditions [#]_. For details on usage and output, see the
+        notes below.
+
+        Parameters
+        ----------
+        eggs : function
+            Given the current `x` it returns the tuple of energy and gradient.
+        spam : function, *optional*
+            Callback function which is given the current `x` and iteration
+            counter each iteration (default: None).
+        a : {4-tuple}, *optional*
+            Numbers obeying 0 < a1 ~ a2 < 1 ~ a3 < a4 that modify the step
+            widths (default: (0.2,0.5,1,2)).
+        c : {2-tuple}, *optional*
+            Numbers obeying 0 < c1 < c2 < 1 that specify the Wolfe-conditions
+            (default: (1E-4,0.9)).
+        note : bool, *optional*
+            Indicates whether notes are printed or not (default: False).
+
+        See Also
+        --------
+        scipy.optimize.fmin_cg, scipy.optimize.fmin_ncg,
+        scipy.optimize.fmin_l_bfgs_b
+
+        Notes
+        -----
+        After initialization by `__init__`, the minimizer is started by calling
+        it using `__call__`, which takes additional parameters. Notifications,
+        if enabled, will state the iteration number, current step width `alpha`,
+        current maximal change `delta` that is compared to the tolerance, and
+        the convergence level if changed. The minimizer will exit in three
+        states: DEAD if no step width above 1E-13 is accepted, QUIT if the
+        maximum number of iterations is reached, or DONE if convergence is
+        achieved. Returned will be the latest `x` and the latest convergence
+        level, which can evaluate ``True`` for all exit states.
+
+        References
+        ----------
+        .. [#] J. Nocedal and S. J. Wright, Springer 2006, "Numerical
+            Optimization", ISBN: 978-0-387-30303-1 (print) / 978-0-387-40065-5
+            `(online) <http://link.springer.com/book/10.1007/978-0-387-40065-5/page/1>`_
+
+        Examples
+        --------
+        >>> def egg(x):
+        ...     E = 0.5*x.dot(x) # energy E(x) -- a two-dimensional parabola
+        ...     g = x # gradient
+        ...     return E,g
+        >>> x = field(point_space(2), val=[1, 3])
+        >>> x,convergence = steepest_descent(egg, note=True)(x0=x, tol=1E-4, clevel=3)
+        iteration : 00000001   alpha = 1.0E+00   delta = 6.5E-01
+        iteration : 00000002   alpha = 2.0E+00   delta = 1.4E-01
+        iteration : 00000003   alpha = 1.6E-01   delta = 2.1E-03
+        iteration : 00000004   alpha = 2.6E-03   delta = 3.0E-04
+        iteration : 00000005   alpha = 2.0E-04   delta = 5.3E-05   convergence level : 1
+        iteration : 00000006   alpha = 8.2E-05   delta = 4.4E-06   convergence level : 2
+        iteration : 00000007   alpha = 6.6E-06   delta = 3.1E-06   convergence level : 3
+        ... done.
+        >>> bool(convergence)
+        True
+        >>> x.val # approximately zero
+        array([ -6.87299426e-07  -2.06189828e-06])
+
+        Attributes
+        ----------
+        x : field
+            Current field.
+        eggs : function
+            Given the current `x` it returns the tuple of energy and gradient.
+        spam : function
+            Callback function which is given the current `x` and iteration
+            counter each iteration; can be ``None``.
+        a : {4-tuple}
+            Numbers obeying 0 < a1 ~ a2 < 1 ~ a3 < a4 that modify the step
+            widths (default: (0.2,0.5,1,2)).
+        c : {2-tuple}
+            Numbers obeying 0 < c1 < c2 < 1 that specify the Wolfe-conditions
+            (default: (1E-4,0.9)).
+        note : notification
+            Notification instance.
+
+    """
+    def __init__(self,eggs,spam=None,a=(0.2,0.5,1,2),c=(1E-4,0.9),note=False):
+        """
+            Initializes the steepest_descent and sets the attributes (except
+            for `x`).
+
+            Parameters
+            ----------
+            eggs : function
+                Given the current `x` it returns the tuple of energy and gradient.
+            spam : function, *optional*
+                Callback function which is given the current `x` and iteration
+                counter each iteration (default: None).
+            a : {4-tuple}, *optional*
+                Numbers obeying 0 < a1 ~ a2 < 1 ~ a3 < a4 that modify the step
+                widths (default: (0.2,0.5,1,2)).
+            c : {2-tuple}, *optional*
+                Numbers obeying 0 < c1 < c2 < 1 that specify the Wolfe-conditions
+                (default: (1E-4,0.9)).
+            note : bool, *optional*
+                Indicates whether notes are printed or not (default: False).
+
+        """
+        self.eggs = eggs ## returns energy and gradient
+
+        self.spam = spam ## serves as callback given x and iteration number
+        self.a = a ## 0 < a1 ~ a2 < 1 ~ a3 < a4
+        self.c = c ## 0 < c1 < c2 < 1
+        self.note = notification(default=bool(note))
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def __call__(self,x0,alpha=1,tol=1E-4,clevel=8,limii=100000):
+        """
+            Runs the steepest descent minimization.
+
+            Parameters
+            ----------
+            x0 : field
+                Starting guess for the minimization.
+            alpha : scalar, *optional*
+                Starting step width to be multiplied with normalized gradient
+                (default: 1).
+            tol : scalar, *optional*
+                Tolerance specifying convergence; measured by maximal change in
+                `x` (default: 1E-4).
+            clevel : integer, *optional*
+                Number of times the tolerance should be undershot before
+                exiting (default: 8).
+            limii : integer, *optional*
+                Maximum number of iterations performed (default: 100,000).
+
+            Returns
+            -------
+            x : field
+                Latest `x` of the minimization.
+            convergence : integer
+                Latest convergence level indicating whether the minimization
+                has converged or not.
+
+        """
+        if(not isinstance(x0,field)):
+            raise TypeError(about._errors.cstring("ERROR: invalid input."))
+        self.x = x0
+
+        clevel = int(clevel)
+        limii = int(limii)
+
+        E,g = self.eggs(self.x) ## energy and gradient
+        norm = g.norm() ## gradient norm
+
+        convergence = 0
+        ii = 1
+        while(True):
+            x_,E,g,alpha,a = self._get_alpha(E,g,norm,alpha) ## "news",alpha,a
+
+            if(alpha is None):
+                self.note.cprint("\niteration : %08u   alpha < 1.0E-13\n... dead."%ii)
+                break
+            else:
+                delta = np.absolute(g.val).max()*(alpha/norm)
+                self.note.cflush("\niteration : %08u   alpha = %3.1E   delta = %3.1E"%(ii,alpha,delta))
+                ## update
+                self.x = x_
+                alpha *= a
+
+            norm = g.norm() ## gradient norm
+            if(ii==limii):
+                self.note.cprint("\n... quit.")
+                break
+            elif(delta<tol):
+                convergence += 1
+                self.note.cflush("   convergence level : %u"%convergence)
+                if(convergence==clevel):
+                    convergence += int(ii==clevel)
+                    self.note.cprint("\n... done.")
+                    break
+            else:
+                convergence = max(0,convergence-1)
+
+            if(self.spam is not None):
+                self.spam(self.x,ii)
+
+            ii += 1
+
+        if(self.spam is not None):
+            self.spam(self.x,ii)
+
+        return self.x,convergence
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _get_alpha(self,E,g,norm,alpha): ## > determines the new alpha
+        while(True):
+            ## Wolfe conditions
+            wolfe,x_,E_,g_,a = self._check_wolfe(E,g,norm,alpha)
+#            wolfe,x_,E_,g_,a = self._check_strong_wolfe(E,g,norm,alpha)
+            if(wolfe):
+                return x_,E_,g_,alpha,a
+            else:
+                alpha *= a
+                if(alpha<1E-13):
+                    return None,None,None,None,None
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _check_wolfe(self,E,g,norm,alpha): ## > checks the Wolfe conditions
+        x_ = self._get_x(g,norm,alpha)
+        pg = norm
+        E_,g_ = self.eggs(x_)
+        if(E_>E+self.c[0]*alpha*pg):
+            if(E_<E):
+                return True,x_,E_,g_,self.a[1]
+            return False,None,None,None,self.a[0]
+        pg_ = g.dot(g_)/norm
+        if(pg_<self.c[1]*pg):
+            return True,x_,E_,g_,self.a[3]
+        return True,x_,E_,g_,self.a[2]
+
+#    def _check_strong_wolfe(self,E,g,norm,alpha): ## > checks the strong Wolfe conditions
+#        x_ = self._get_x(g,norm,alpha)
+#        pg = norm
+#        E_,g_ = self.eggs(x_)
+#        if(E_>E+self.c[0]*alpha*pg):
+#            if(E_<E):
+#                return True,x_,E_,g_,self.a[1]
+#            return False,None,None,None,self.a[0]
+#        apg_ = np.absolute(g.dot(g_))/norm
+#        if(apg_>self.c[1]*np.absolute(pg)):
+#            return True,x_,E_,g_,self.a[3]
+#        return True,x_,E_,g_,self.a[2]
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def _get_x(self,g,norm,alpha): ## > updates x
+        return self.x-g*(alpha/norm)
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def __repr__(self):
+        return "<nifty.steepest_descent>"
+
+##=============================================================================
+
diff --git a/powerspectrum.py b/powerspectrum.py
index 1d051b294848e76d71a2361a09ac8e672148374d..de135bb57975b8ee1407224f57a83e146769fdb2 100644
--- a/powerspectrum.py
+++ b/powerspectrum.py
@@ -65,7 +65,7 @@ def draw_vector_nd(axes,dgrid,ps,symtype=0,fourier=False,zerocentered=False,kpac
 
     """
     if(kpack is None):
-        kdict = np.fft.fftshift(nkdict(axes,dgrid,fourier))
+        kdict = np.fft.fftshift(nkdict_fast(axes,dgrid,fourier))
         klength = nklength(kdict)
     else:
         kdict = kpack[1][np.fft.ifftshift(kpack[0],axes=shiftaxes(zerocentered,st_to_zero_mode=False))]
@@ -164,7 +164,7 @@ def draw_vector_nd(axes,dgrid,ps,symtype=0,fourier=False,zerocentered=False,kpac
 #        foufield = field
 #    fieldabs = np.abs(foufield)**2
 #
-#    kdict = nkdict(axes,dgrid,fourier)
+#    kdict = nkdict_fast(axes,dgrid,fourier)
 #    klength = nklength(kdict)
 #
 #    ## power spectrum
@@ -228,7 +228,7 @@ def calc_ps_fast(field,axes,dgrid,zerocentered=False,fourier=False,pindex=None,k
     if(rho is None):
         if(pindex is None):
             ## kdict
-            kdict = nkdict(axes,dgrid,fourier)
+            kdict = nkdict_fast(axes,dgrid,fourier)
             ## klength
             if(kindex is None):
                 klength = nklength(kdict)
@@ -253,7 +253,7 @@ def calc_ps_fast(field,axes,dgrid,zerocentered=False,fourier=False,pindex=None,k
                 rho[pindex[ii]] += 1
     elif(pindex is None):
         ## kdict
-        kdict = nkdict(axes,dgrid,fourier)
+        kdict = nkdict_fast(axes,dgrid,fourier)
         ## klength
         if(kindex is None):
             klength = nklength(kdict)
@@ -317,9 +317,9 @@ def get_power_index(axes,dgrid,zerocentered,irred=False,fourier=True):
 
     ## kdict, klength
     if(np.any(zerocentered==False)):
-        kdict = np.fft.fftshift(nkdict(axes,dgrid,fourier),axes=shiftaxes(zerocentered,st_to_zero_mode=True))
+        kdict = np.fft.fftshift(nkdict_fast(axes,dgrid,fourier),axes=shiftaxes(zerocentered,st_to_zero_mode=True))
     else:
-        kdict = nkdict(axes,dgrid,fourier)
+        kdict = nkdict_fast(axes,dgrid,fourier)
     klength = nklength(kdict)
     ## output
     if(irred):
@@ -372,9 +372,9 @@ def get_power_indices(axes,dgrid,zerocentered,fourier=True):
 
     ## kdict, klength
     if(np.any(zerocentered==False)):
-        kdict = np.fft.fftshift(nkdict(axes,dgrid,fourier),axes=shiftaxes(zerocentered,st_to_zero_mode=True))
+        kdict = np.fft.fftshift(nkdict_fast(axes,dgrid,fourier),axes=shiftaxes(zerocentered,st_to_zero_mode=True))
     else:
-        kdict = nkdict(axes,dgrid,fourier)
+        kdict = nkdict_fast(axes,dgrid,fourier)
     klength = nklength(kdict)
     ## output
     ind = np.empty(axes,dtype=np.int)
@@ -587,13 +587,11 @@ def shiftaxes(zerocentered,st_to_zero_mode=False):
 
 
 def nkdict(axes,dgrid,fourier=True):
-
     """
         Calculates an n-dimensional array with its entries being the lengths of
         the k-vectors from the zero point of the Fourier grid.
 
     """
-
     if(fourier):
         dk = dgrid
     else:
@@ -605,6 +603,25 @@ def nkdict(axes,dgrid,fourier=True):
     return kdict
 
 
+def nkdict_fast(axes,dgrid,fourier=True):
+    """
+        Calculates an n-dimensional array with its entries being the lengths of
+        the k-vectors from the zero point of the Fourier grid.
+
+    """
+    if(fourier):
+        dk = dgrid
+    else:
+        dk = np.array([1/dgrid[i]/axes[i] for i in range(len(axes))])
+
+    temp_vecs = np.array(np.where(np.ones(axes)),dtype='float').reshape(np.append(len(axes),axes))
+    temp_vecs = np.rollaxis(temp_vecs,0,len(temp_vecs.shape))
+    temp_vecs -= axes//2
+    temp_vecs *= dk
+    temp_vecs *= temp_vecs
+    return np.sqrt(np.sum((temp_vecs),axis=-1))
+
+
 def nklength(kdict):
     return np.sort(list(set(kdict.flatten())))
 
diff --git a/setup.py b/setup.py
index cba071fac868d869d528c6097d84977aab358422..db6741fd0f8d6f8a90a6d67796a1d5c985a8a54c 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@ from distutils.core import setup
 import os
 
 setup(name="nifty",
-      version="0.4.2",
+      version="0.6.0",
       description="Numerical Information Field Theory",
       author="Marco Selig",
       author_email="mselig@mpa-garching.mpg.de",