From 7d9aaa5e09c979d55d26e7d4e9e2d1be5f7b9e61 Mon Sep 17 00:00:00 2001
From: Marco Selig <mselig@ncg-02.MPA-Garching.MPG.DE>
Date: Fri, 25 Apr 2014 16:52:27 +0200
Subject: [PATCH] demos updated.

---
 demos/demo_excaliwir.py | 416 +++++++++++++++++-----------------------
 demos/demo_faraday.py   |  99 +++++-----
 demos/demo_wf1.py       |  37 ++--
 demos/demo_wf2.py       |  40 ++--
 demos/demo_wf3.py       |   1 -
 nifty_core.py           | 380 ++++++++++++++++++------------------
 6 files changed, 459 insertions(+), 514 deletions(-)

diff --git a/demos/demo_excaliwir.py b/demos/demo_excaliwir.py
index 159c137cf..3aa48b892 100644
--- a/demos/demo_excaliwir.py
+++ b/demos/demo_excaliwir.py
@@ -28,270 +28,206 @@
     ..  /__/ /__/ /__/ /__/    \___/  \___   /  demo
     ..                               /______/
 
-    NIFTY demo for (critical) Wiener filtering of Gaussian random signals.
+    NIFTY demo for (extended) critical Wiener filtering of Gaussian random signals.
 
 """
 from __future__ import division
 from nifty import *
-from scipy.sparse.linalg import LinearOperator as lo
-from scipy.sparse.linalg import cg
 
 
-note = notification()
+##=============================================================================
 
+class problem(object):
 
-##-----------------------------------------------------------------------------
+    def __init__(self, x_space, s2n=2, **kwargs):
+        """
+            Sets up a Wiener filter problem.
+
+            Parameters
+            ----------
+            x_space : space
+                Position space the signal lives in.
+            s2n : float, *optional*
+                Signal-to-noise ratio (default: 2).
 
-## spaces
-r1 = rg_space(512,1,zerocenter=False)
-r2 = rg_space(64,2)
-h = hp_space(16)
-g = gl_space(48)
-z = s_space = k = k_space = p = d_space = None
-
-## power spectrum (and more)
-power = kindex = rho = powerindex = powerundex = None
-
-## operators
-S = Sk = R = N = Nj = D = None
-
-## fields
-s = n = d = j = m = None
-
-## propagator class
-class propagator_operator(operator):
-    """
-        This is the information propagator from the Wiener filter formula.
-        It is defined by its inverse. It is given the prior signal covariance S,
-        the noise covariance N and the response R in para.
-    """
-    def _inverse_multiply(self,x):
-        ## The inverse can be calculated directly
-        S,N,R = self.para
-        return S.inverse_times(x)+R.adjoint_times(N.inverse_times(R.times(x)))
-
-    ## the inverse multiplication and multiplication with S modified to return 1d arrays
-    _matvec = (lambda self,x: self.inverse_times(x).val.flatten())
-    _precon = (lambda self,x: self.para[0].times(x).val.flatten())
-
-    def _multiply(self,x):
         """
-            the operator is defined by its inverse, so multiplication has to be
-            done by inverting the inverse numerically using the conjugate gradient
-            method from scipy
+        ## set signal space
+        self.z = x_space
+        ## set conjugate space
+        self.k = self.z.get_codomain()
+        self.k.set_power_indices(**kwargs)
+
+        ## set some power spectrum
+        self.power = (lambda k: 42 / (k + 1) ** 3)
+
+        ## define signal covariance
+        self.S = power_operator(self.k, spec=self.power, bare=True)
+        ## define projector to spectral bands
+        self.Sk = self.S.get_projection_operator()
+        ## generate signal
+        self.s = self.S.get_random_field(domain=self.z)
+
+        ## define response
+        self.R = response_operator(self.z, sigma=0.0, mask=1.0)
+        ## get data space
+        d_space = self.R.target
+
+        ## define noise covariance
+        self.N = diagonal_operator(d_space, diag=abs(s2n) * self.s.var(), bare=True)
+        ## define (plain) projector
+        self.Nj = projection_operator(d_space)
+        ## generate noise
+        n = self.N.get_random_field(domain=d_space)
+
+        ## compute data
+        self.d = self.R(self.s) + n
+
+        ## define information source
+        self.j = self.R.adjoint_times(self.N.inverse_times(self.d), target=self.k)
+        ## define information propagator
+        self.D = propagator_operator(S=self.S, N=self.N, R=self.R)
+
+        ## reserve map
+        self.m = None
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def solve(self, newspec=None):
         """
-        A = lo(shape=tuple(self.dim()),matvec=self._matvec,dtype=self.domain.datatype) ## linear operator
-        b = x.val.flatten()
-        x_,info = cg(A,b,x0=None,tol=1.0E-5,maxiter=10*len(x),xtype=None,M=None,callback=None) ## conjugate gradient
-        if(info==0):
-            return x_
-        else:
-            note.cprint("NOTE: conjugate gradient failed.")
-            return None
+            Solves the Wiener filter problem for a given power spectrum
+            reconstructing a signal estimate.
 
-##-----------------------------------------------------------------------------
+            Parameters
+            ----------
+            newspace : {scalar, list, array, field, function}, *optional*
+                Assumed power spectrum (default: k ** -2).
 
+        """
+        ## set (given) power spectrum
+        if(newspec is None):
+            newspec = np.r_[1, 1 / self.k.power_indices["kindex"][1:] ** 2] ## Laplacian
+        elif(newspec is False):
+            newspec = self.power ## assumed to be known
+        self.S.set_power(newspec, bare=True)
 
+        ## reconstruct map
+        self.m = self.D(self.j, W=self.S, tol=1E-3, note=False)
 
-##-----------------------------------------------------------------------------
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-def setup(space,s2n=3,nvar=None):
-    """
-        sets up the spaces, operators and fields
-
-        Parameters
-        ----------
-        space : space
-            the signal lives in `space`
-        s2n : positive number, *optional*
-            `s2n` is the signal to noise ratio (default: 3)
-        nvar = positive number, *optional*
-            the noise variance, `nvar` will be calculated according to
-            `s2n` if not specified (default: None)
-    """
-    global z,s_space,k,k_space,p,d_space,power,kindex,rho,powerindex,powerundex,S,Sk,R,N,Nj,D,s,n,d,j,m
-
-    ## signal space
-    z = s_space = space
-
-    ## conjugate space
-    k = k_space = s_space.get_codomain()
-    ## the power indices are calculated once and saved
-    kindex,rho,powerindex,powerundex = k_space.get_power_indices()
-
-    ## power spectrum
-    power = (lambda kk: 42/(kk+1)**3)
-
-    ## prior signal covariance operator (power operator)
-    S = power_operator(k_space,spec=power)
-    ## projection operator to the spectral bands
-    Sk = S.get_projection_operator()
-    ## the Gaussian random field generated from its power operator S
-    s = S.get_random_field(domain=s_space,target=k_space)
-
-    ## response
-    R = response_operator(s_space,sigma=0,mask=1)
-    ## data space
-    p = d_space = R.target
-
-    ## calculating the noise covariance
-    if(nvar is None):
-        svar = np.var(s.val) ## given unit response
-        nvar = svar/s2n**2
-    ## noise covariance operator
-    N = diagonal_operator(d_space,diag=nvar,bare=True)
-    ## Gaussian noise generated from its covariance N
-    n = N.get_random_field(domain=d_space,target=d_space)
-
-    ## data
-    d = R(s)+n
+    def solve_critical(self, newspec=None, q=0, alpha=1, delta=1, epsilon=0):
+        """
+            Solves the (generalised) Wiener filter problem
+            reconstructing a signal estimate and a power spectrum.
+
+            Parameters
+            ----------
+            newspace : {scalar, list, array, field, function}, *optional*
+                Initial power spectrum (default: k ** -2).
+            q : {scalar, list, array}, *optional*
+                Spectral scale parameter of the assumed inverse-Gamme prior
+                (default: 0).
+            alpha : {scalar, list, array}, *optional*
+                Spectral shape parameter of the assumed inverse-Gamme prior
+                (default: 1).
+            delta : float, *optional*
+                First filter perception parameter (default: 1).
+            epsilon : float, *optional*
+                Second filter perception parameter (default: 0).
+
+            See Also
+            --------
+            infer_power
 
-##-----------------------------------------------------------------------------
+        """
+        ## set (initial) power spectrum
+        if(newspec is None):
+            newspec = np.r_[1, 1 / self.k.power_indices["kindex"][1:] ** 2] ## Laplacian
+        elif(newspec is False):
+            newspec = self.power ## assumed to be known
+        self.S.set_power(newspec, bare=True)
 
-##=============================================================================
+        ## pre-compute denominator
+        denominator = self.k.power_indices["rho"] + 2 * (alpha - 1 + abs(epsilon))
 
-def run(space=r1,s2n=3,nvar=None,**kwargs):
-    """
-        runs the demo of the generalised Wiener filter
-
-        Parameters
-        ----------
-        space : space, *optional*
-            `space` can be any space from nifty, that supports the plotting
-            routine (default: r1 = rg_space(512,1,zerocenter=False))
-        s2n : positive number, *optional*
-            `s2n` is the signal to noise (default: 3)
-        nvar : positive number, *optional*
-            the noise variance, `nvar` will be calculated according to
-            `s2n` if not specified (default: None)
-    """
-    global s_space,k_space,d_space,power,S,Sk,R,N,Nj,D,s,n,d,j,m
-
-    ## setting up signal, noise, data and the operators S, N and R
-    setup(space,s2n=s2n,nvar=nvar)
-
-    ## information source
-    j = R.adjoint_times(N.inverse_times(d))
-
-    ## information propagator
-    D = propagator_operator(s_space,sym=True,imp=True,para=[S,N,R])
-
-    ## reconstructed map
-    m = D(j)
-    if(m is None):
-        return None
-
-    ## fields
-    s.plot(title="signal",**kwargs)
-#    n.cast_domain(s_space,newtarget=k_space)
-#    n.plot(title="noise",**kwargs)
-#    n.cast_domain(d_space,newtarget=d_space)
-    d.cast_domain(s_space,newtarget=k_space)
-    d.plot(title="data",vmin=np.min(s.val),vmax=np.max(s.val),**kwargs)
-    d.cast_domain(d_space,newtarget=d_space)
-    m.plot(title="reconstructed map",vmin=np.min(s.val),vmax=np.max(s.val),**kwargs)
-
-    ## power spectrum
-#    s.plot(title="power spectra",power=True,other=(m,power),mono=False)
-
-    ## uncertainty
-#    uncertainty = D.hat(bare=True,nrun=D.domain.dim()//4,target=k_space)
-#    if(np.all(uncertainty.val>0)):
-#        sqrt(uncertainty).plot(title="standard deviation",**kwargs)
+        ## iterate
+        iterating = True
+        while(iterating):
+
+            ## reconstruct map
+            self.m = self.D(self.j, W=self.S, tol=1E-3, note=False)
+            if(self.m is None):
+                break
+
+            ## reconstruct power spectrum
+            tr_B1 = self.Sk.pseudo_tr(self.m) ## == Sk(m).pseudo_dot(m)
+            tr_B2 = self.Sk.pseudo_tr(self.D, loop=True)
+
+            numerator = 2 * q + tr_B1 + abs(delta) * tr_B2 ## non-bare(!)
+            power = numerator / denominator
+
+            ## check convergence
+            dtau = np.log(power / self.S.get_power())
+            iterating = (np.max(np.abs(dtau)) > 2E-2)
+
+            ## update signal covariance
+            self.S.set_power(power, bare=False) ## auto-updates D
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def plot(self):
+        """
+            Produces plots.
+
+        """
+        ## plot signal
+        self.s.plot(title="signal")
+        ## plot data
+        try:
+            d_ = field(self.z, val=self.d.val, target=self.k)
+            d_.plot(title="data", vmin=self.s.min(), vmax=self.s.max())
+        except:
+            pass
+        ## plot map
+        if(self.m is None):
+            self.s.plot(power=True, mono=False, other=self.power)
+        else:
+            self.m.plot(title="reconstructed map", vmin=self.s.min(), vmax=self.s.max())
+            self.m.plot(power=True, mono=False, other=(self.power, self.S.get_power()))
 
 ##=============================================================================
 
 ##-----------------------------------------------------------------------------
 
-def run_critical(space=r2,s2n=3,nvar=None,q=1E-12,alpha=1,perception=[1,0],**kwargs):
-    """
-        runs the demo of the critical generalised Wiener filter
-
-        Parameters
-        ----------
-        space : space, *optional*
-            `space` can be any space from nifty, that supports the plotting
-            routine (default: r2 = rg_space(64,2))
-        s2n : positive number, *optional*
-            `s2n` is the signal to noise (default: 3)
-        nvar : positive number, *optional*
-            the noise variance, `nvar` will be calculated according to
-            `s2n` if not specified (default: None)
-        q : positive number, *optional*
-            `q` is the minimal power on all scales (default: 1E-12)
-        alpha : a number >= 1, *optional*
-            `alpha` = 1 means Jeffreys prior for the power spectrum (default: 1)
-        perception : array of shape (2,1), *optional*
-            perception[0] is delta, perception[1] is epsilon. They are tuning
-            factors for the filter (default: [1,0])
-
-        See Also
-        --------
-        infer_power
-
-    """
-    global s_space,k_space,d_space,power,rho,S,Sk,R,N,Nj,D,s,n,d,j,m
-
-    ## setting up signal, noise, data and the operators S, N and R
-    setup(space,s2n=s2n,nvar=nvar)
-    if(perception[1] is None):
-        perception[1] = rho/2*(perception[0]-1)
-
-    ## information source
-    j = R.adjoint_times(N.inverse_times(d))
-
-    ## unknown power spectrum, the power operator is given an initial value
-    S.set_power(42,bare=True) ## The answer is 42. I double checked.
-    ## the power spectrum is drawn from the first guess power operator
-    pk = S.get_power(bare=False) ## non-bare(!)
-
-    ## information propagator
-    D = propagator_operator(s_space,sym=True,imp=True,para=[S,N,R])
-
-    ## iterative reconstruction of the power spectrum and the map
-    iteration = 0
-    while(True):
-        iteration += 1
-
-        ## the Wiener filter reconstruction using the current power spectrum
-        m = D(j)
-        if(m is None):
-            return None
-
-        ## measuring a new estimated power spectrum from the current reconstruction
-        b1 = Sk.pseudo_tr(m) ## == Sk(m).pseudo_dot(m), but faster
-        b2 = Sk.pseudo_tr(D,nrun=np.sqrt(Sk.domain.dim())//4) ## probing of the partial traces of D
-        pk_new = (2*q+b1+perception[0]*b2)/(rho+2*(alpha-1+perception[1])) ## non-bare(!)
-        pk_new = smooth_power(pk_new,domain=k_space,mode="2s",exclude=min(8,len(rho))) ## smoothing
-        ## the power operator is given the new spectrum
-        S.set_power(pk_new,bare=False) ## auto-updates D
-
-        ## check convergence
-        log_change = np.max(np.abs(log(pk_new/pk)))
-        if(log_change<=0.01):
-            note.cprint("NOTE: desired accuracy reached in iteration %u."%(iteration))
-            break
-        else:
-            note.cprint("NOTE: log-change == %4.3f ( > 1%% ) in iteration %u."%(log_change,iteration))
-            pk = np.copy(pk_new)
-
-    ## fields
-    s.plot(title="signal",**kwargs)
-#    n.cast_domain(s_space,newtarget=k_space)
-#    n.plot(title="noise",**kwargs)
-#    n.cast_domain(d_space,newtarget=d_space)
-    d.cast_domain(s_space,newtarget=k_space)
-    d.plot(title="data",vmin=np.min(s.val),vmax=np.max(s.val),**kwargs)
-    d.cast_domain(d_space,newtarget=d_space)
-    m.plot(title="reconstructed map",vmin=np.min(s.val),vmax=np.max(s.val),**kwargs)
-
-    ## power spectrum
-    s.plot(title="power spectra",power=True,other=(S.get_power(),power),mono=False)
-
-    ## uncertainty
-#    uncertainty = D.hat(bare=True,nrun=D.domain.dim()//4,target=k_space)
-#    if(np.all(uncertainty.val>0)):
-#        sqrt(uncertainty).plot(title="standard deviation")
+if(__name__=="__main__"):
+#    pl.close("all")
+
+    ## define signal space
+    x_space = rg_space(128)
+
+    ## setup problem
+    p = problem(x_space, log=True)
+    ## solve problem given some power spectrum
+    p.solve()
+    ## solve problem
+    p.solve_critical()
+
+    p.plot()
+
+    ## retrieve objects
+    k_space = p.k
+    power = p.power
+    S = p.S
+    Sk = p.Sk
+    s = p.s
+    R = p.R
+    d_space = p.R.target
+    N = p.N
+    Nj = p.Nj
+    d = p.d
+    j = p.j
+    D = p.D
+    m = p.m
 
 ##-----------------------------------------------------------------------------
 
diff --git a/demos/demo_faraday.py b/demos/demo_faraday.py
index b89b8a954..ee8037853 100644
--- a/demos/demo_faraday.py
+++ b/demos/demo_faraday.py
@@ -47,24 +47,11 @@ about.infos.off()
 
 ##-----------------------------------------------------------------------------
 
-## spaces
-h  = hp_space(128)
-l  = lm_space(383)
-g  = gl_space(384) ## nlon == 767
-g_ = gl_space(384, nlon=768)
-r  = rg_space([768, 384], dist=[1/360, 1/180])
-r_ = rg_space([256, 128], dist=[1/120, 1/60])
-
-## map
-m = field(h, val=np.load("demo_faraday_map.npy"))
-
-## projection operator
-Sk = None
+# (global) Faraday map
+m = field(hp_space(128), val=np.load("demo_faraday_map.npy"))
 
 ##-----------------------------------------------------------------------------
 
-
-
 ##=============================================================================
 
 def run(projection=False, power=False):
@@ -74,47 +61,71 @@ def run(projection=False, power=False):
         Parameters
         ----------
         projection : bool, *optional*
-            Whether to additionaly include projections in the demo or not. If
-            ``projection == True`` the projection operator `Sk` will be
-            defined. (default: False)
+            Whether to additionaly show projections or not (default: False).
         power : bool, *optional*
-            Whether to additionaly show power spectra in the demo or not.
-            (default: False)
+            Whether to additionaly show power spectra or not (default: False).
 
     """
-    global Sk
-    ## start in hp_space
+    nicely = {"vmin":-4, "vmax":4, "cmap":ncmap.fm()}
+
+    # (a) representation on HEALPix grid
     m0 = m
+    m0.plot(title=r"$m$ on a HEALPix grid", **nicely)
+    nicely.update({"cmap":ncmap.fm()}) # healpy bug workaround
 
-    ## transform to lm_space
-    m1 = m0.transform(l)
+    # (b) representation in spherical harmonics
+    k_space = m0.target # == lm_space(383, mmax=383)
+    m1 = m0.transform(k_space) # == m.transform()
+#    m1.plot(title=r"$m$ in spherical harmonics")
+
+    if(power):
+        m1.plot(title=r"angular power spectrum of $m$", vmin=1E-2, vmax=1E+1, mono=False)
     if(projection):
-        ## define projection operator
-        Sk = projection_operator(l)
-        ## project quadrupole
+        # define projection operator
+        Sk = projection_operator(m1.domain)
+        # project quadrupole
         m2 = Sk(m0, band=2)
+        m2.plot(title=r"angular quadrupole of $m$ on a HEALPix grid", **nicely)
 
-    ## transform to gl_space
-    m3 = m1.transform(g)
+    # (c) representation on Gauss-Legendre grid
+    y_space = m.target.get_codomain(coname="gl") # == gl_space(384, nlon=767)
+    m3 = m1.transform(y_space) # == m0.transform().transform(y_space)
+    m3.plot(title=r"$m$ on a spherical Gauss-Legendre grid", **nicely)
 
-    ## transform to rg_space
-    m4 = m1.transform(g_) ## auxiliary gl_space
-    m4.cast_domain(r) ## rg_space cast
-    m4.set_val(np.roll(m4.val[::-1, ::-1], g.nlon()//2, axis=1)) ## rearrange
-    if(power):
-        ## restrict to central window
-        m5 = field(r_, val=m4[128:256, 256:512]).transform()
+    if(projection):
+        m4 = Sk(m3, band=2)
+        m4.plot(title=r"angular quadrupole of $m$ on a Gauss-Legendre grid", **nicely)
+
+    # (d) representation on regular grid
+    y_space = gl_space(384, nlon=768) # auxiliary gl_space
+    z_space = rg_space([768, 384], dist=[1/360, 1/180])
+    m5 = m1.transform(y_space)
+    m5.cast_domain(z_space)
+    m5.set_val(np.roll(m5.val[::-1, ::-1], y_space.nlon()//2, axis=1)) # rearrange value array
+    m5.plot(title=r"$m$ on a regular 2D grid", **nicely)
 
-    ## plots
-    m0.plot(title=r"$m$ on a HEALPix grid", vmin=-4, vmax=4, cmap=ncmap.fm())
     if(power):
-        m1.plot(title=r"angular power spectrum of $m$", vmin=1E-2, vmax=1E+1, mono=False)
+        m5.target.set_power_indices(log=False)
+        m5.plot(power=True, title=r"Fourier power spectrum of $m$", vmin=1E-3, vmax=1E+0, mono=False)
     if(projection):
-        m2.plot(title=r"quadrupole of $m$ on a HEALPix grid", vmin=-4, vmax=4, cmap=ncmap.fm())
-    m3.plot(title=r"$m$ on a spherical Gauss-Legendre grid", vmin=-4, vmax=4, cmap=ncmap.fm())
-    m4.plot(title=r"$m$ on a regular 2D grid", vmin=-4, vmax=4, cmap=ncmap.fm())
-    if(power):
-        m5.plot(title=r"(restricted, binned) Fourier power spectrum of $m$", vmin=1E-3, vmax=1E+0, mono=False, log=True)
+        m5.target.set_power_indices(log=False)
+        # define projection operator
+        Sk = projection_operator(m5.target)
+        # project quadrupole
+        m6 = Sk(m5, band=2)
+        m6.plot(title=r"Fourier quadrupole of $m$ on a regular 2D grid", **nicely)
 
 ##=============================================================================
 
+##-----------------------------------------------------------------------------
+
+if(__name__=="__main__"):
+#    pl.close("all")
+
+    # run demo
+    run(projection=False, power=False)
+    # define projection operator
+    Sk = projection_operator(m.target)
+
+##-----------------------------------------------------------------------------
+
diff --git a/demos/demo_wf1.py b/demos/demo_wf1.py
index 580f25ac8..ac96c0b0b 100644
--- a/demos/demo_wf1.py
+++ b/demos/demo_wf1.py
@@ -32,36 +32,39 @@
 
 """
 from __future__ import division
-from nifty import *                                                   # version 0.6.0
+from nifty import *                                              # version 0.8.0
 
 
 # some signal space; e.g., a two-dimensional regular grid
-x_space = rg_space([256, 256])                                        # define signal space
+x_space = rg_space([128, 128])                                   # define signal space
+#x_space = rg_space(512)
+#x_space = hp_space(32)
+#x_space = gl_space(96)
 
-k_space = x_space.get_codomain()                                      # get conjugate 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
+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
+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
+# some noise variance; e.g., signal-to-noise ratio of 1
+N = diagonal_operator(d_space, diag=s.var(), bare=True)          # define noise covariance
+n = N.get_random_field(domain=d_space)                           # generate noise
 
-d = R(s) + n                                                          # compute data
+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
+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
+m = D(j, W=S, tol=1E-3, note=True)                               # reconstruct map
 
-s.plot(title="signal")                                                # plot signal
+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
+d_.plot(title="data", vmin=s.min(), vmax=s.max())                # plot data
+m.plot(title="reconstructed map", vmin=s.min(), vmax=s.max())    # plot map
 
diff --git a/demos/demo_wf2.py b/demos/demo_wf2.py
index 8d0b03500..2d9f81983 100644
--- a/demos/demo_wf2.py
+++ b/demos/demo_wf2.py
@@ -32,31 +32,31 @@
 
 """
 from __future__ import division
-from nifty import *                                                   # version 0.6.0
+from nifty import *                                              # version 0.8.0
 
 
 # some signal space; e.g., a two-dimensional regular grid
-x_space = rg_space([256, 256])                                        # define signal space
+x_space = rg_space([128, 128])                                   # define signal space
 
-k_space = x_space.get_codomain()                                      # get conjugate 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
+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
+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
+# some noise variance; e.g., signal-to-noise ratio of 1
+N = diagonal_operator(d_space, diag=s.var(), bare=True)          # define noise covariance
+n = N.get_random_field(domain=d_space)                           # generate noise
 
-d = R(s) + n                                                          # compute data
+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
+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):
@@ -65,16 +65,16 @@ def eggs(x):
 
     """
     DIx = D.inverse_times(x)
-    H = 0.5 * DIx.dot(x) - j.dot(x)                                    # compute information Hamiltonian
-    g = DIx - j                                                        # compute its gradient
+    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)
+m = field(x_space, target=k_space)                               # reconstruct map
+m, convergence = steepest_descent(eggs=eggs, note=True)(m, tol=1E-3, clevel=3)
 
-s.plot(title="signal")                                                # plot signal
+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
+d_.plot(title="data", vmin=s.min(), vmax=s.max())                # plot data
+m.plot(title="reconstructed map", vmin=s.min(), vmax=s.max())    # plot map
 
diff --git a/demos/demo_wf3.py b/demos/demo_wf3.py
index 5b6038056..b9215591e 100644
--- a/demos/demo_wf3.py
+++ b/demos/demo_wf3.py
@@ -33,7 +33,6 @@
 """
 from __future__ import division
 from nifty import *                                                   # version 0.7.0
-from nifty.nifty_explicit import *
 
 
 # some signal space; e.g., a one-dimensional regular grid
diff --git a/nifty_core.py b/nifty_core.py
index 1312131be..e39ce06c3 100644
--- a/nifty_core.py
+++ b/nifty_core.py
@@ -1100,73 +1100,6 @@ class space(object):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-#    def get_power_index(self,irreducible=False): ## TODO: remove in future version
-#        """
-#            **DEPRECATED** Provides the indexing array of the power spectrum.
-#
-#            Provides either an array giving for each component of a field the
-#            corresponding index of a power spectrum (if ``irreducible==False``)
-#            or two arrays containing the scales of the modes and the numbers of
-#            modes with this scale (if ``irreducible==True``).
-#
-#            Parameters
-#            ----------
-#            irreducible : bool, *optional*
-#                Whether to return two arrays containing the scales and
-#                corresponding number of represented modes (if True) or the
-#                indexing array (if False) (default: False).
-#
-#            Returns
-#            -------
-#            kindex : numpy.ndarray
-#                Scale of each band, returned only if ``irreducible==True``.
-#            rho : numpy.ndarray
-#                Number of modes per scale represented in the discretization,
-#                returned only if ``irreducible==True``.
-#            pindex : numpy.ndarray
-#                Indexing array giving the power spectrum index for each
-#                represented mode, returned only if ``irreducible==False``.
-#
-#            Notes
-#            -----
-#            The indexing array is of the same shape as a field living in this
-#            space and contains the indices of the associated bands.
-#            kindex and rho are each one-dimensional arrays.
-#        """
-#        about.warnings.cprint("WARNING: 'get_power_index' is deprecated.")
-#        raise NotImplementedError(about._errors.cstring("ERROR: no generic instance method 'get_power_index'."))
-
-    def get_power_undex(self,pindex=None): ## TODO: remove in future version
-        """
-            **DEPRECATED** Provides the Unindexing array for an indexed power spectrum.
-
-            Parameters
-            ----------
-            pindex : numpy.ndarray, *optional*
-                Indexing array giving the power spectrum index for each
-                represented mode.
-
-            Returns
-            -------
-            pundex : numpy.ndarray
-                Unindexing array undoing power indexing.
-
-            Notes
-            -----
-            Indexing with the unindexing array undoes the indexing with the
-            indexing array; i.e., ``power == power[pindex].flatten()[pundex]``.
-
-            See Also
-            --------
-            get_power_index
-
-        """
-        about.warnings.cprint("WARNING: 'get_power_undex' is deprecated.")
-        if(pindex is None):
-            pindex = self.get_power_index(irreducible=False)
-#        return list(np.unravel_index(np.unique(pindex,return_index=True,return_inverse=False)[1],pindex.shape,order='C')) ## < version 0.4
-        return np.unique(pindex,return_index=True,return_inverse=False)[1]
-
     def set_power_indices(self,**kwargs):
         """
             Sets the (un)indexing objects for spectral indexing internally.
@@ -1985,14 +1918,6 @@ class point_space(space):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-#    def get_power_index(self,irreducible=False): ## TODO: remove in future version
-#        """
-#            **DEPRECATED** Raises an error since the power spectrum is
-#            ill-defined for point spaces.
-#        """
-#        about.warnings.cprint("WARNING: 'get_power_index' is deprecated.")
-#        raise AttributeError(about._errors.cstring("ERROR: power spectra ill-defined for (unstructured) point spaces."))
-
     def set_power_indices(self,**kwargs):
         """
             Raises
@@ -2502,45 +2427,6 @@ class rg_space(space):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-#    def get_power_index(self,irreducible=False):  ## TODO: remove in future version
-#        """
-#            **DEPRECATED** Provides the indexing array of the power spectrum.
-#
-#            Provides either an array giving for each component of a field the
-#            corresponding index of a power spectrum (if ``irreducible==False``)
-#            or two arrays containing the scales of the modes and the numbers of
-#            modes with this scale (if ``irreducible==True``).
-#
-#            Parameters
-#            ----------
-#            irreducible : bool, *optional*
-#                Whether to return two arrays containing the scales and
-#                corresponding number of represented modes (if True) or the
-#                indexing array (if False) (default: False).
-#
-#            Returns
-#            -------
-#            kindex : numpy.ndarray
-#                Scale of each band, returned only if ``irreducible==True``.
-#            rho : numpy.ndarray
-#                Number of modes per scale represented in the discretization,
-#                returned only if ``irreducible==True``.
-#            pindex : numpy.ndarray
-#                Indexing array giving the power spectrum index for each
-#                represented mode, returned only if ``irreducible==False``.
-#
-#            Notes
-#            -----
-#            The indexing array is of the same shape as a field living in this
-#            space and contains the indices of the associated bands.
-#            kindex and rho are each one-dimensional arrays.
-#        """
-#        about.warnings.cprint("WARNING: 'get_power_index' is deprecated.")
-#        if(self.fourier):
-#            return gp.get_power_index(self.para[:(np.size(self.para)-1)//2],self.vol,self.para[-((np.size(self.para)-1)//2):].astype(np.bool),irred=irreducible,fourier=self.fourier) ## nontrivial
-#        else:
-#            raise AttributeError(about._errors.cstring("ERROR: power spectra indexing ill-defined."))
-
     def set_power_indices(self,**kwargs):
         """
             Sets the (un)indexing objects for spectral indexing internally.
@@ -3613,46 +3499,6 @@ class lm_space(space):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    def get_power_index(self,irreducible=False): ## TODO: remove in future version
-        """
-            **DEPRECATED** Provides the indexing array of the power spectrum.
-
-            Provides either an array giving for each component of a field the
-            corresponding index of a power spectrum (if ``irreducible==False``)
-            or two arrays containing the scales of the modes and the numbers of
-            modes with this scale (if ``irreducible==True``).
-
-            Parameters
-            ----------
-            irreducible : bool, *optional*
-                Whether to return two arrays containing the scales and
-                corresponding number of represented modes (if True) or the
-                indexing array (if False) (default: False).
-
-            Returns
-            -------
-            kindex : numpy.ndarray
-                Scale of each band, returned only if ``irreducible==True``.
-            rho : numpy.ndarray
-                Number of modes per scale represented in the discretization,
-                returned only if ``irreducible==True``.
-            pindex : numpy.ndarray
-                Indexing array giving the power spectrum index for each
-                represented mode, returned only if ``irreducible==False``.
-
-            Notes
-            -----
-            The indexing array is of the same shape as a field living in this
-            space and contains the indices of the associated bands.
-            kindex and rho are each one-dimensional arrays.
-        """
-        about.warnings.cprint("WARNING: 'get_power_index' is deprecated.")
-        if(irreducible):
-            ind = np.arange(self.para[0]+1)
-            return ind,2*ind+1
-        else:
-            return hp.Alm.getlm(self.para[0],i=None)[0] ## l of (l,m)
-
     def set_power_indices(self,**kwargs):
         """
             Sets the (un)indexing objects for spectral indexing internally.
@@ -4451,15 +4297,6 @@ class gl_space(space):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-#    def get_power_index(self,irreducible=False): ## TODO: remove in future version
-#        """
-#            **DEPRECATED** Raises an error since the power spectrum for a field on the sphere
-#            is defined via the spherical harmonics components and not its
-#            position-space representation.
-#        """
-#        about.warnings.cprint("WARNING: 'get_power_index' is deprecated.")
-#        raise AttributeError(about._errors.cstring("ERROR: power spectra indexing ill-defined."))
-
     def set_power_indices(self,**kwargs):
         """
             Raises
@@ -5094,15 +4931,6 @@ class hp_space(space):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-#    def get_power_index(self,irreducible=False): ## TODO: remove in future version
-#        """
-#            **DEPRECATED** Raises an error since the power spectrum for a field on the sphere
-#            is defined via the spherical harmonics components and not its
-#            position-space representation.
-#        """
-#        about.warnings.cprint("WARNING: 'get_power_index' is deprecated.")
-#        raise AttributeError(about._errors.cstring("ERROR: power spectra indexing ill-defined."))
-
     def set_power_indices(self,**kwargs):
         """
             Raises
@@ -5636,14 +5464,6 @@ class nested_space(space):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-#    def get_power_index(self,irreducible=False): ## TODO: remove in future version
-#        """
-#            **DEPRECATED** Raises an error since there is no canonical
-#            definition for the power spectrum on a generic product space.
-#        """
-#        about.warnings.cprint("WARNING: 'get_power_index' is deprecated.")
-#        raise AttributeError(about._errors.cstring("ERROR: power spectra indexing ill-defined."))
-
     def set_power_indices(self,**kwargs):
         """
             Raises
@@ -6332,22 +6152,21 @@ class field(object):
 
     def dim(self,split=False):
         """
-        Computes the (array) dimension of the underlying space.
+            Computes the (array) dimension of the underlying space.
 
-        Parameters
-        ----------
-        split : bool
-            Sets the output to be either split up per axis or
-            in form of total number of field entries in all
-            dimensions (default=False)
+            Parameters
+            ----------
+            split : bool
+                Sets the output to be either split up per axis or
+                in form of total number of field entries in all
+                dimensions (default=False)
 
-        Returns
-        -------
-        dim : {scalar, ndarray}
-            Dimension of space.
+            Returns
+            -------
+            dim : {scalar, ndarray}
+                Dimension of space.
 
         """
-
         return self.domain.dim(split=split)
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -6893,6 +6712,183 @@ class field(object):
 
     ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
+    def min(self,ignore=False,**kwargs):
+        """
+            Returns the minimum of the field values.
+
+            Parameters
+            ----------
+            ignore : bool
+                Whether to ignore NANs or not (default: False).
+
+            Returns
+            -------
+            amin : {scalar, ndarray}
+                Minimum field value.
+
+            See Also
+            --------
+            np.amin, np.nanmin
+
+        """
+        if(ignore):
+            return np.nanmin(self.val,**kwargs)
+        else:
+            return np.amin(self.val,**kwargs)
+
+    def max(self,ignore=False,**kwargs):
+        """
+            Returns the maximum of the field values.
+
+            Parameters
+            ----------
+            ignore : bool
+                Whether to ignore NANs or not (default: False).
+
+            Returns
+            -------
+            amax : {scalar, ndarray}
+                Maximum field value.
+
+            See Also
+            --------
+            np.amax, np.nanmax
+
+        """
+        if(ignore):
+            return np.nanmax(self.val,**kwargs)
+        else:
+            return np.amax(self.val,**kwargs)
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    def med(self,**kwargs):
+        """
+            Returns the median of the field values.
+
+            Returns
+            -------
+            med : scalar
+                Median field value.
+
+            See Also
+            --------
+            np.median
+
+        """
+        return np.median(self.val,**kwargs)
+
+    def mean(self,**kwargs):
+        """
+            Returns the mean of the field values.
+
+            Returns
+            -------
+            mean : scalar
+                Mean field value.
+
+            See Also
+            --------
+            np.mean
+
+        """
+        return np.mean(self.val,**kwargs)
+
+    def std(self,**kwargs):
+        """
+            Returns the standard deviation of the field values.
+
+            Returns
+            -------
+            std : scalar
+                Standard deviation of the field values.
+
+            See Also
+            --------
+            np.std
+
+        """
+        return np.std(self.val,**kwargs)
+
+    def var(self,**kwargs):
+        """
+            Returns the variance of the field values.
+
+            Returns
+            -------
+            var : scalar
+                Variance of the field values.
+
+            See Also
+            --------
+            np.var
+
+        """
+        return np.var(self.val,**kwargs)
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+    def argmin(self,split=True,**kwargs):
+        """
+            Returns the index of the minimum field value.
+
+            Parameters
+            ----------
+            split : bool
+                Whether to split (unravel) the flat index or not; does not
+                apply to multiple indices along some axis (default: True).
+
+            Returns
+            -------
+            ind : {integer, tuple, array}
+                Index of the minimum field value being an integer for
+                one-dimensional fields, a tuple for multi-dimensional fields,
+                and an array in case minima along some axis are requested.
+
+            See Also
+            --------
+            np.argmax, np.argmin
+
+        """
+        ind = np.argmin(self.val,**kwargs)
+        if(split)and(np.isscalar(ind)):
+            ind = np.unravel_index(ind,self.val.shape,order='C')
+            if(len(ind)==1):
+                return ind[0]
+        return ind
+
+    def argmax(self,split=True,**kwargs):
+        """
+            Returns the index of the maximum field value.
+
+            Parameters
+            ----------
+            split : bool
+                Whether to split (unravel) the flat index or not; does not
+                apply to multiple indices along some axis (default: True).
+
+            Returns
+            -------
+            ind : {integer, tuple, array}
+                Index of the maximum field value being an integer for
+                one-dimensional fields, a tuple for multi-dimensional fields,
+                and an array in case maxima along some axis are requested.
+
+            See Also
+            --------
+            np.argmax, np.argmin
+
+        """
+        ind = np.argmax(self.val,**kwargs)
+        if(split)and(np.isscalar(ind)):
+            ind = np.unravel_index(ind,self.val.shape,order='C')
+            if(len(ind)==1):
+                return ind[0]
+        return ind
+
+    ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
     def __len__(self):
         return int(self.dim(split=True)[0])
 
-- 
GitLab