From ac8f1fe281df90a13b1893df4d5472da75de1b9c Mon Sep 17 00:00:00 2001
From: Theo Steininger <theo.steininger@ultimanet.de>
Date: Wed, 12 Jul 2017 00:11:41 +0200
Subject: [PATCH] Refactored critical power files.

---
 demos/critical_filtering.py                   |   9 +-
 .../critical_filter/critical_power_energy.py  | 103 +++++++++---------
 .../wiener_filter/wiener_filter_energy.py     |   5 +-
 .../invertible_operator_mixin.py              |   3 +-
 .../smoothness_operator.py                    |  38 ++++---
 nifty/sugar.py                                |  15 +--
 6 files changed, 88 insertions(+), 85 deletions(-)

diff --git a/demos/critical_filtering.py b/demos/critical_filtering.py
index 8ca2cf927..9a9e2103f 100644
--- a/demos/critical_filtering.py
+++ b/demos/critical_filtering.py
@@ -123,9 +123,6 @@ if __name__ == "__main__":
                        callback=convergence_measure,
                        max_history_length=3)
 
-    inverter = ConjugateGradient(convergence_level=1,
-                                 convergence_tolerance=10e-4,
-                                 preconditioner=None)
     # Setting starting position
     flat_power = Field(p_space,val=10e-8)
     m0 = flat_power.power_synthesize(real_signal=True)
@@ -144,7 +141,7 @@ if __name__ == "__main__":
         D0 = map_energy.curvature
         m0 = D0.inverse_times(j)
         # Initializing the power energy with updated parameters
-        power_energy = CriticalPowerEnergy(position=t0, m=m0, D=D0, sigma=10., samples=3, inverter=inverter)
+        power_energy = CriticalPowerEnergy(position=t0, m=m0, D=D0, smoothness_prior=10., samples=3)
 
         (power_energy, convergence) = minimizer1(power_energy)
 
@@ -153,6 +150,8 @@ if __name__ == "__main__":
         t0.val  = power_energy.position.val.real
 
         # Plotting current estimate
-        plot_parameters(m0,t0,log(sp), data_power)
+        print i
+        if i%50 == 0:
+            plot_parameters(m0,t0,log(sp), data_power)
 
 
diff --git a/nifty/library/critical_filter/critical_power_energy.py b/nifty/library/critical_filter/critical_power_energy.py
index bddac6bb9..7848aee59 100644
--- a/nifty/library/critical_filter/critical_power_energy.py
+++ b/nifty/library/critical_filter/critical_power_energy.py
@@ -1,3 +1,5 @@
+import numpy as np
+
 from nifty.energies.energy import Energy
 from nifty.operators.smoothness_operator import SmoothnessOperator
 from nifty.library.critical_filter import CriticalPowerCurvature
@@ -10,9 +12,10 @@ from nifty import Field, exp
 class CriticalPowerEnergy(Energy):
     """The Energy of the power spectrum according to the critical filter.
 
-    It describes the energy of the logarithmic amplitudes of the power spectrum of
-    a Gaussian random field under reconstruction uncertainty with smoothness and
-    inverse gamma prior. It is used to infer the correlation structure of a correlated signal.
+    It describes the energy of the logarithmic amplitudes of the power spectrum
+    of a Gaussian random field under reconstruction uncertainty with smoothness
+    and inverse gamma prior. It is used to infer the correlation structure of a
+    correlated signal.
 
     Parameters
     ----------
@@ -25,20 +28,21 @@ class CriticalPowerEnergy(Energy):
         If not specified, the map is assumed to be no reconstruction.
         default : None
     alpha : float
-        The spectral prior of the inverse gamma distribution. 1.0 corresponds to
-        non-informative.
+        The spectral prior of the inverse gamma distribution. 1.0 corresponds
+        to non-informative.
         default : 1.0
     q : float
-        The cutoff parameter of the inverse gamma distribution. 0.0 corresponds to
-        non-informative.
+        The cutoff parameter of the inverse gamma distribution. 0.0 corresponds
+        to non-informative.
+        default : 0.0
+    smoothness_prior : float
+        Controls the strength of the smoothness prior
         default : 0.0
-    sigma : float
-        The parameter of the smoothness prior.
-        default : ??? None? ???????
     logarithmic : boolean
         Whether smoothness acts on linear or logarithmic scale.
     samples : integer
-        Number of samples used for the estimation of the uncertainty corrections.
+        Number of samples used for the estimation of the uncertainty
+        corrections.
         default : 3
     w : Field
         The contribution from the map with or without uncertainty. It is used
@@ -49,76 +53,72 @@ class CriticalPowerEnergy(Energy):
         default : None
     """
 
-    def __init__(self, position, m, D=None, alpha =1.0, q=0., sigma=0.,
-                 logarithmic = True, samples=3, w=None, inverter=None):
-        super(CriticalPowerEnergy, self).__init__(position = position)
+    def __init__(self, position, m, D=None, alpha=1.0, q=0.,
+                 smoothness_prior=0., logarithmic=True, samples=3, w=None):
+        super(CriticalPowerEnergy, self).__init__(position=position)
         self.m = m
         self.D = D
         self.samples = samples
-        self.sigma = sigma
+        self.smoothness_prior = np.float(smoothness_prior)
         self.alpha = Field(self.position.domain, val=alpha)
         self.q = Field(self.position.domain, val=q)
-        self.T = SmoothnessOperator(domain=self.position.domain[0], sigma=self.sigma,
+        self.T = SmoothnessOperator(domain=self.position.domain[0],
+                                    strength=self.smoothness_prior,
                                     logarithmic=logarithmic)
         self.rho = self.position.domain[0].rho
-        self.inverter = inverter
-        self.w = w
-        if self.w is None:
-            self.w = self._calculate_w(self.m, self.D, self.samples)
-        self.theta = (exp(-self.position) * (self.q + self.w / 2.))
-
+        self._w = w if w is not None else None
 
     def at(self, position):
-        return self.__class__(position, self.m, D=self.D,
-                              alpha =self.alpha,
-                              q=self.q,
-                              sigma=self.sigma, w=self.w,
-                              samples=self.samples)
+        return self.__class__(position, self.m, D=self.D, alpha=self.alpha,
+                              q=self.q, smoothness_prior=self.smoothness_prior,
+                              w=self.w, samples=self.samples)
 
     @property
     def value(self):
-        energy = self._theta.vdot(Field(self.position.domain,val=1.), bare= True)
+        energy = self._theta.sum()
         energy += self.position.vdot(self._rho_prime, bare=True)
         energy += 0.5 * self.position.vdot(self._Tt)
         return energy.real
 
     @property
     def gradient(self):
-        gradient = - self._theta.weight(-1)
+        gradient = -self._theta.weight(-1)
         gradient += (self._rho_prime).weight(-1)
-        gradient +=  self._Tt
+        gradient += self._Tt
         gradient.val = gradient.val.real
         return gradient
 
     @property
     def curvature(self):
-        curvature = CriticalPowerCurvature(theta=self._theta.weight(-1), T=self.T,
-                                           inverter=self.inverter)
+        curvature = CriticalPowerCurvature(theta=self._theta.weight(-1),
+                                           T=self.T)
         return curvature
 
-    def _calculate_w(self, m, D, samples):
-        w = Field(domain=self.position.domain, val=0. , dtype=m.dtype)
-        if D is not None:
-            for i in range(samples):
-                posterior_sample = generate_posterior_sample(m, D, inverter = self.inverter)
-                projected_sample = posterior_sample.power_analyze(
-                    logarithmic=self.position.domain[0].config["logarithmic"],
-                    nbin= self.position.domain[0].config["nbin"])
-                w += (projected_sample) * self.rho
-            w /= float(samples)
-        else:
-            w = m.power_analyze(
-                    logarithmic=self.position.domain[0].config["logarithmic"],
-                        nbin=self.position.domain[0].config["nbin"])
-            w *= self.rho
-
-        return w
-
+    @property
+    def w(self):
+        if self._w is None:
+            w = Field(domain=self.position.domain, val=0., dtype=self.m.dtype)
+            if self.D is not None:
+                for i in range(self.samples):
+                    posterior_sample = generate_posterior_sample(
+                                                            self.m, self.D)
+                    projected_sample = posterior_sample.power_analyze(
+                     logarithmic=self.position.domain[0].config["logarithmic"],
+                     nbin=self.position.domain[0].config["nbin"])
+                    w += (projected_sample) * self.rho
+                w /= float(self.samples)
+            else:
+                w = self.m.power_analyze(
+                     logarithmic=self.position.domain[0].config["logarithmic"],
+                     nbin=self.position.domain[0].config["nbin"])
+                w *= self.rho
+            self._w = w
+        return self._w
 
     @property
     @memo
     def _theta(self):
-        return (exp(-self.position) * (self.q + self.w / 2.))
+        return exp(-self.position) * (self.q + self.w / 2.)
 
     @property
     @memo
@@ -129,4 +129,3 @@ class CriticalPowerEnergy(Energy):
     @memo
     def _Tt(self):
         return self.T(self.position)
-
diff --git a/nifty/library/wiener_filter/wiener_filter_energy.py b/nifty/library/wiener_filter/wiener_filter_energy.py
index 798319e59..8eec64c51 100644
--- a/nifty/library/wiener_filter/wiener_filter_energy.py
+++ b/nifty/library/wiener_filter/wiener_filter_energy.py
@@ -29,7 +29,6 @@ class WienerFilterEnergy(Energy):
         self.R = R
         self.N = N
         self.S = S
-        self.inverter = inverter
 
     def at(self, position):
         return self.__class__(position=position, d=self.d, R=self.R, N=self.N,
@@ -48,10 +47,8 @@ class WienerFilterEnergy(Energy):
     @property
     @memo
     def curvature(self):
-        return WienerFilterCurvature(R=self.R, N=self.N, S=self.S,
-                                     inverter=self.inverter)
+        return WienerFilterCurvature(R=self.R, N=self.N, S=self.S)
 
-    @property
     @memo
     def _Dx(self):
         return self.curvature(self.position)
diff --git a/nifty/operators/invertible_operator_mixin/invertible_operator_mixin.py b/nifty/operators/invertible_operator_mixin/invertible_operator_mixin.py
index e134656b4..ad41ba0b7 100644
--- a/nifty/operators/invertible_operator_mixin/invertible_operator_mixin.py
+++ b/nifty/operators/invertible_operator_mixin/invertible_operator_mixin.py
@@ -38,7 +38,8 @@ class InvertibleOperatorMixin(object):
         (default: ConjugateGradient)
 
     preconditioner : LinearOperator
-        Preconditions the minimizaion problem
+        Preconditioner that is used by ConjugateGraduent if no minimizer was
+        given.
 
     Attributes
     ----------
diff --git a/nifty/operators/smoothness_operator/smoothness_operator.py b/nifty/operators/smoothness_operator/smoothness_operator.py
index ae7efd294..e9e354536 100644
--- a/nifty/operators/smoothness_operator/smoothness_operator.py
+++ b/nifty/operators/smoothness_operator/smoothness_operator.py
@@ -5,14 +5,17 @@ from nifty.operators.laplace_operator import LaplaceOperator
 
 
 class SmoothnessOperator(EndomorphicOperator):
-    """An operator measuring the smoothness on an irregular grid with respect to some scale.
+    """An operator measuring the smoothness on an irregular grid with respect
+    to some scale.
 
-    This operator applies the irregular LaplaceOperator and its adjoint to some Field over a
-    PowerSpace which corresponds to its smoothness and weights the result with a scale parameter sigma.
-    It is used in the smoothness prior terms of the CriticalPowerEnergy. For this purpose we
-    use free boundary conditions in the LaplaceOperator, having no curvature at both ends.
-    In addition the first entry is ignored as well, corresponding to the overall mean of the map.
-    The mean is therefore not considered in the smoothness prior.
+    This operator applies the irregular LaplaceOperator and its adjoint to some
+    Field over a PowerSpace which corresponds to its smoothness and weights the
+    result with a scale parameter sigma. It is used in the smoothness prior
+    terms of the CriticalPowerEnergy. For this purpose we use free boundary
+    conditions in the LaplaceOperator, having no curvature at both ends. In
+    addition the first entry is ignored as well, corresponding to the overall
+    mean of the map. The mean is therefore not considered in the smoothness
+    prior.
 
 
     Parameters
@@ -26,7 +29,8 @@ class SmoothnessOperator(EndomorphicOperator):
 
     # ---Overwritten properties and methods---
 
-    def __init__(self, domain, sigma, logarithmic=True, default_spaces=None):
+    def __init__(self, domain, strength=1., logarithmic=True,
+                 default_spaces=None):
 
         super(SmoothnessOperator, self).__init__(default_spaces=default_spaces)
 
@@ -37,10 +41,10 @@ class SmoothnessOperator(EndomorphicOperator):
         if not isinstance(self.domain[0], PowerSpace):
             raise TypeError("The domain must contain exactly one PowerSpace.")
 
-        if sigma <= 0:
+        if strength <= 0:
             raise ValueError("ERROR: invalid sigma.")
 
-        self._sigma = sigma
+        self._strength = strength
 
         self._laplace = LaplaceOperator(domain=self.domain,
                                         logarithmic=logarithmic)
@@ -68,11 +72,17 @@ class SmoothnessOperator(EndomorphicOperator):
         return False
 
     def _times(self, x, spaces):
-        res = self._laplace.adjoint_times(self._laplace(x, spaces), spaces)
-        return (1./self.sigma)**2*res
+        if self._strength != 0:
+            result = self._laplace.adjoint_times(self._laplace(x, spaces),
+                                                 spaces)
+            result *= self._strength**2
+        else:
+            result = x.copy_empty()
+            result.val[:] = 0
+        return result
 
     # ---Added properties and methods---
 
     @property
-    def sigma(self):
-        return self._sigma
+    def strength(self):
+        return self._strength
diff --git a/nifty/sugar.py b/nifty/sugar.py
index 5114a949f..b1835af91 100644
--- a/nifty/sugar.py
+++ b/nifty/sugar.py
@@ -64,11 +64,13 @@ def create_power_operator(domain, power_spectrum, dtype=None,
     return DiagonalOperator(domain, diagonal=f, bare=True)
 
 
-def generate_posterior_sample(mean, covariance, inverter=None):
-    """ Generates a posterior sample from a Gaussian distribution with given mean and covariance
+def generate_posterior_sample(mean, covariance):
+    """ Generates a posterior sample from a Gaussian distribution with given
+    mean and covariance
 
-    This method generates samples by setting up the observation and reconstruction of a mock signal
-    in order to obtain residuals of the right correlation which are added to the given mean.
+    This method generates samples by setting up the observation and
+    reconstruction of a mock signal in order to obtain residuals of the right
+    correlation which are added to the given mean.
 
     Parameters
     ----------
@@ -77,9 +79,6 @@ def generate_posterior_sample(mean, covariance, inverter=None):
     covariance : WienerFilterCurvature
         The posterior correlation structure consisting of a
         response operator, noise covariance and prior signal covariance
-    inverter : ConjugateGradient *optional*
-        the conjugate gradient used to invert the curvature for the Wiener filter.
-        default : None
 
     Returns
     -------
@@ -91,8 +90,6 @@ def generate_posterior_sample(mean, covariance, inverter=None):
     S = covariance.S
     R = covariance.R
     N = covariance.N
-    if inverter is None:
-        inverter = ConjugateGradient(preconditioner=S)
 
     power = S.diagonal().power_analyze()**.5
     mock_signal = power.power_synthesize(real_signal=True)
-- 
GitLab