From ce40f30f1fcba313e212825e82dc60f9d0d3f90c Mon Sep 17 00:00:00 2001 From: kjako <jakob@knollmueller.de> Date: Fri, 19 May 2017 16:24:15 +0200 Subject: [PATCH] all ingredients for critical filtering, nothing tested --- nifty/field.py | 146 +++++++++++++++++- .../energy_library/critical_power_energy.py | 46 +++--- nifty/library/operator_library/__init__.py | 4 +- .../critical_power_curvature.py | 17 +- .../operator_library/laplace_operator.py | 60 +++++++ .../operator_library/smoothness_operator.py | 61 ++++++++ 6 files changed, 302 insertions(+), 32 deletions(-) create mode 100644 nifty/library/operator_library/laplace_operator.py diff --git a/nifty/field.py b/nifty/field.py index 4933c95e9..2f00b9656 100644 --- a/nifty/field.py +++ b/nifty/field.py @@ -258,7 +258,145 @@ class Field(Loggable, Versionable, object): return result_field - def _calculate_power_spectrum(self, x, pindex, rho, axes=None): + def project_power(self, spaces=None, logarithmic=False, nbin=None, + binbounds=None, decompose_power=True): + """ Computes the quadratic power projection for a subspace of the Field. + + Creates a PowerSpace for the space addressed by `spaces` with the given + binning and computes the quadratic power projection as a Field over this + PowerSpace. This can only be done if the subspace to be analyzed is a + harmonic space. + + Parameters + ---------- + spaces : int *optional* + The subspace for which the power projection shall be computed + (default : None). + if spaces==None : Tries to synthesize for the whole domain + logarithmic : boolean *optional* + True if the output PowerSpace should use logarithmic binning. + {default : False} + nbin : int *optional* + The number of bins the resulting PowerSpace shall have + (default : None). + if nbin==None : maximum number of bins is used + binbounds : array-like *optional* + Inner bounds of the bins (default : None). + if binbounds==None : bins are inferred. Overwrites nbins and log + decompose_power : boolean, *optional* + Whether the analysed signal-space Field is intrinsically real or + complex and if the projected power shall therefore be computed + for the real and the imaginary part of the Field separately + (default : True). + + Raise + ----- + ValueError + Raised if + *len(domain) is != 1 when spaces==None + *len(spaces) is != 1 if not None + *the analyzed space is not harmonic + + Returns + ------- + out : Field + The output object. It's domain is a PowerSpace and it contains + the quadratic power projection of 'self's field. + + See Also + -------- + power_synthesize, PowerSpace, power_analyze + + """ + + # check if all spaces in `self.domain` are either harmonic or + # power_space instances + for sp in self.domain: + if not sp.harmonic and not isinstance(sp, PowerSpace): + self.logger.info( + "Field has a space in `domain` which is neither " + "harmonic nor a PowerSpace.") + + # check if the `spaces` input is valid + spaces = utilities.cast_axis_to_tuple(spaces, len(self.domain)) + if spaces is None: + if len(self.domain) == 1: + spaces = (0,) + else: + raise ValueError( + "Field has multiple spaces as domain " + "but `spaces` is None.") + + if len(spaces) == 0: + raise ValueError( + "No space for analysis specified.") + elif len(spaces) > 1: + raise ValueError( + "Conversion of only one space at a time is allowed.") + + space_index = spaces[0] + + if not self.domain[space_index].harmonic: + raise ValueError( + "The analyzed space must be harmonic.") + + # Create the target PowerSpace instance: + # If the associated signal-space field was real, we extract the + # hermitian and anti-hermitian parts of `self` and put them + # into the real and imaginary parts of the projected power. + # If it was complex, all the power is put into a real power projection + + distribution_strategy = \ + self.val.get_axes_local_distribution_strategy( + self.domain_axes[space_index]) + + harmonic_domain = self.domain[space_index] + power_domain = PowerSpace(harmonic_partner=harmonic_domain, + distribution_strategy=distribution_strategy, + logarithmic=logarithmic, nbin=nbin, + binbounds=binbounds) + + # extract pindex and rho from power_domain + pindex = power_domain.pindex + + if decompose_power: + hermitian_part, anti_hermitian_part = \ + harmonic_domain.hermitian_decomposition( + self.val, + axes=self.domain_axes[space_index]) + + [hermitian_power, anti_hermitian_power] = \ + [self._calculate_projected_power( + x=part, + pindex=pindex, + axes=self.domain_axes[space_index]) + for part in [hermitian_part, anti_hermitian_part]] + + power_projection = hermitian_power + 1j * anti_hermitian_power + else: + power_projection = self._calculate_projected_power( + x=self.val, + pindex=pindex, + axes=self.domain_axes[space_index]) + + # create the result field and put power_spectrum into it + result_domain = list(self.domain) + result_domain[space_index] = power_domain + + if decompose_power: + result_dtype = np.complex + else: + result_dtype = np.float + + result_field = self.copy_empty( + domain=result_domain, + dtype=result_dtype, + distribution_strategy=power_projection.distribution_strategy) + result_field.set_val(new_val=power_projection, copy=False) + + return result_field + + def _calculate_projected_power(self, x, pindex, axes=None): fieldabs = abs(x) fieldabs **= 2 @@ -268,8 +406,12 @@ class Field(Loggable, Versionable, object): target_shape=x.shape, target_strategy=x.distribution_strategy, axes=axes) - power_spectrum = pindex.bincount(weights=fieldabs, + projected_power = pindex.bincount(weights=fieldabs, axis=axes) + return projected_power + + def _calculate_power_spectrum(self, x, pindex, rho, axes=None): + power_spectrum = self._calculate_projected_power(x, pindex, axes) if axes is not None: new_rho_shape = [1, ] * len(power_spectrum.shape) new_rho_shape[axes[0]] = len(rho) diff --git a/nifty/library/energy_library/critical_power_energy.py b/nifty/library/energy_library/critical_power_energy.py index 751659be6..6a012219b 100644 --- a/nifty/library/energy_library/critical_power_energy.py +++ b/nifty/library/energy_library/critical_power_energy.py @@ -1,7 +1,8 @@ from nifty.energies.energy import Energy -from nifty.library.operator_library import CriticalPowerCurvature +from nifty.library.operator_library import CriticalPowerCurvature,\ + SmoothnessOperator from nifty.sugar import generate_posterior_sample -from nifty import Field +from nifty import Field, exp class CriticalPowerEnergy(Energy): """The Energy for the Gaussian lognormal case. @@ -21,35 +22,44 @@ class CriticalPowerEnergy(Energy): The prior signal covariance in harmonic space. """ - def __init__(self, position, m, D=None, alpha =1.0, q=0, w=None, samples=3): + def __init__(self, position, m, D=None, alpha =1.0, q=0, sigma=0, w=None, samples=3): super(CriticalPowerEnergy, self).__init__(position = position) self.m = m + self.D = D + self.samples = samples + self.sigma = sigma + self.alpha = alpha + self.q = q + self.T = SmoothnessOperator(domain=self.position.domain, sigma=self.sigma) + self.rho = self.position.domain.rho if w is None: - self._calculate_w(self.m, D) + self.w = self._calculate_w(self.m, self.D, self.samples) + self.theta = exp(-self.position) * (self.q + w / 2.) def at(self, position): - return self.__class__(position, self.d, self.R, self.N, self.S) + return self.__class__(position, self.m, D=self.D, + alpha =self.alpha, + q=self.q, + sigma=self.sigma, w=self.w, + samples=self.samples) @property def value(self): - energy = 0.5 * self.position.dot(self.S.inverse_times(self.position)) - energy += 0.5 * (self.d - self.R(self.position)).dot( - self.N.inverse_times(self.d - self.R(self.position))) + energy = self.theta.sum() + energy += self.position.dot(self.alpha - 1 + self.rho / 2.) + energy += 0.5 * self.position.dot(self.T(self.position)) return energy.real @property def gradient(self): - gradient = self.S.inverse_times(self.position) - gradient -= self.R.derived_adjoint_times( - self.N.inverse_times(self.d - self.R(self.position)), self.position) + gradient = - self.theta + gradient += self.alpha - 1 + self.rho / 2. + gradient += self.T(self.position) return gradient @property def curvature(self): - curvature =CriticalPowerCurvature(R=self.R, - N=self.N, - S=self.S, - position=self.position) + curvature = CriticalPowerCurvature(theta=self.theta, T = self.T) return curvature def _calculate_w(self, m, D, samples): @@ -57,13 +67,13 @@ class CriticalPowerEnergy(Energy): if D is not None: for i in range(samples): posterior_sample = generate_posterior_sample(m, D) - projected_sample =posterior_sample.power_analyze()**2 + projected_sample =posterior_sample.project_power(domain=self.position.domain) w += projected_sample w /= float(samples) else: - pass + w = m.project_power(domain=self.position.domain) - return w / float(samples) + return w diff --git a/nifty/library/operator_library/__init__.py b/nifty/library/operator_library/__init__.py index 2f1160296..b9a3cfa0a 100644 --- a/nifty/library/operator_library/__init__.py +++ b/nifty/library/operator_library/__init__.py @@ -1,3 +1,5 @@ from wiener_filter_curvature import WienerFilterCurvature from nonlinear_wiener_filter_curvature import NonlinearWienerFilterCurvature -from critical_power_curvature import CriticalPowerCurvature \ No newline at end of file +from critical_power_curvature import CriticalPowerCurvature +from laplace_operator import LaplaceOperator +from smoothness_operator import SmoothnessOperator \ No newline at end of file diff --git a/nifty/library/operator_library/critical_power_curvature.py b/nifty/library/operator_library/critical_power_curvature.py index 889718fb4..1cbb1f7cf 100644 --- a/nifty/library/operator_library/critical_power_curvature.py +++ b/nifty/library/operator_library/critical_power_curvature.py @@ -3,15 +3,13 @@ from nifty.operators import EndomorphicOperator,\ class CriticalPowerCurvature(InvertibleOperatorMixin, EndomorphicOperator): - def __init__(self, R, N, S, position, inverter=None, preconditioner=None): + def __init__(self, theta, T, inverter=None, preconditioner=None): - self.R = R - self.N = N - self.S = S - self.position = position + self.theta = theta + self.T = T if preconditioner is None: - preconditioner = self.S.times - self._domain = self.S.domain + preconditioner = self.T.times + self._domain = self.T.domain super(CriticalPowerCurvature, self).__init__(inverter=inverter, preconditioner=preconditioner) @property @@ -29,7 +27,4 @@ class CriticalPowerCurvature(InvertibleOperatorMixin, EndomorphicOperator): # ---Added properties and methods--- def _times(self, x, spaces): - return self.R.derived_adjoint_times( - self.N.inverse_times(self.R.derived_times( - x, self.position)), self.position)\ - + self.S.inverse_times(x) + return self.T(x) + self.theta * x diff --git a/nifty/library/operator_library/laplace_operator.py b/nifty/library/operator_library/laplace_operator.py new file mode 100644 index 000000000..f9df140e2 --- /dev/null +++ b/nifty/library/operator_library/laplace_operator.py @@ -0,0 +1,60 @@ + +import numpy as np +from nifty import Field,\ + EndomorphicOperator,\ + PowerSpace +class LaplaceOperator(EndomorphicOperator): + def __init__(self, domain, + default_spaces=None): + super(LaplaceOperator, self).__init__(default_spaces) + if (domain is not None): + if(not isinstance(domain, PowerSpace)): + raise TypeError("The domain has to live over a PowerSpace") + self._domain = self._parse_domain(domain) + """ + input parameters: + domain- can only live over one domain + to do: + correct implementation of D20 object + """ + @property + def target(self): + return self._domain + @property + def domain(self): + return self._domain + @property + def unitary(self): + return False + @property + def symmetric(self): + return False + @property + def self_adjoint(self): + return False + def _times(self, t, spaces): + if t.val.distribution_strategy != 'not': + self.logger.warn("distribution_strategy should be 'not' but is %s" + % t.val.distribution_strategy) + array = t.val.get_full_data() + else: + array = t.val.get_local_data(copy=False) + ret = 2 * array - np.append(0, array[:-1]) - np.append(array[1:], 0) + ret[0] = 0 + ret[1] = 0 + ret[-1] = 0 + return Field(self.domain, val=ret) + def _adjoint_times(self, t, spaces): + if t.val.distribution_strategy != 'not': + self.logger.warn("distribution_strategy should be 'not' but is %s" + % t.val.distribution_strategy) + array = t.val.get_full_data() + else: + array = t.val.get_local_data(copy=False) + ret = 2 * array - np.append(0, array[:-1]) - np.append(array[1:], 0) + ret[0] = 0 + ret[1] = -array[2] + ret[2] = 2*array[2]-array[3] + ret[-1] = -array[-2] + ret[-2] = -array[-3]+2*array[-2] + return Field(self.domain, val=ret) \ No newline at end of file diff --git a/nifty/library/operator_library/smoothness_operator.py b/nifty/library/operator_library/smoothness_operator.py index e69de29bb..fca9d91e2 100644 --- a/nifty/library/operator_library/smoothness_operator.py +++ b/nifty/library/operator_library/smoothness_operator.py @@ -0,0 +1,61 @@ +from nifty import EndomorphicOperator,\ + PowerSpace + +from laplace_operator import LaplaceOperator + + +class SmoothnessOperator(EndomorphicOperator): + + def __init__(self, domain, sigma=1., + default_spaces=None): + + super(SmoothnessOperator, self).__init__(default_spaces) + + if (not isinstance(domain, PowerSpace)): + raise TypeError("The domain has to live over a PowerSpace") + + self._domain = self._parse_domain(domain) + + if (sigma <= 0): + raise ValueError("ERROR: invalid sigma.") + + self._sigma = sigma + + self._Laplace = LaplaceOperator(domain=self._domain[0]) + + """ + SmoothnessOperator + + input parameters: + domain: Domain of the field, has to be a single PowerSpace + sigma: specifying the expected roughness, has to be a positive float + + """ + + @property + def sigma(self): + return self._sigma + + @property + def target(self): + return self._domain + + @property + def domain(self): + return self._domain + + @property + def unitary(self): + return False + + @property + def symmetric(self): + return False + + @property + def self_adjoint(self): + return False + + def _times(self, t, spaces): + res = self._Laplace.adjoint_times(self._Laplace.times(t)) + return (1/self.sigma)**2*res -- GitLab