Commit 4cc0f412 authored by M Selig's avatar M Selig
Browse files

Merge pull request #9 from mselig/develop

version update
parents d4d18b1c 40a174f8
...@@ -99,7 +99,7 @@ Requirements ...@@ -99,7 +99,7 @@ Requirements
Download Download
........ ........
The latest release is tagged **v0.7.0** and is available as a source package The latest release is tagged **v0.8.0** and is available as a source package
at `<https://github.com/mselig/nifty/tags>`_. The current version can be at `<https://github.com/mselig/nifty/tags>`_. The current version can be
obtained by cloning the repository:: obtained by cloning the repository::
......
...@@ -28,270 +28,207 @@ ...@@ -28,270 +28,207 @@
.. /__/ /__/ /__/ /__/ \___/ \___ / demo .. /__/ /__/ /__/ /__/ \___/ \___ / 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 __future__ import division
from nifty import * 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 ## set signal space
done by inverting the inverse numerically using the conjugate gradient self.z = x_space
method from scipy ## 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 Solves the Wiener filter problem for a given power spectrum
b = x.val.flatten() reconstructing a signal estimate.
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
##----------------------------------------------------------------------------- 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): def solve_critical(self, newspec=None, q=0, alpha=1, delta=1, epsilon=0):
""" """
sets up the spaces, operators and fields Solves the (generalised) Wiener filter problem
reconstructing a signal estimate and a power spectrum.
Parameters
---------- Parameters
space : space ----------
the signal lives in `space` newspace : {scalar, list, array, field, function}, *optional*
s2n : positive number, *optional* Initial power spectrum (default: k ** -2).
`s2n` is the signal to noise ratio (default: 3) q : {scalar, list, array}, *optional*
nvar = positive number, *optional* Spectral scale parameter of the assumed inverse-Gamme prior
the noise variance, `nvar` will be calculated according to (default: 0).
`s2n` if not specified (default: None) alpha : {scalar, list, array}, *optional*
""" Spectral shape parameter of the assumed inverse-Gamme prior
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 (default: 1).
delta : float, *optional*
## signal space First filter perception parameter (default: 1).
z = s_space = space epsilon : float, *optional*
Second filter perception parameter (default: 0).
## conjugate space
k = k_space = s_space.get_codomain() See Also
## the power indices are calculated once and saved --------
kindex,rho,powerindex,powerundex = k_space.get_power_indices() infer_power
## 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
##----------------------------------------------------------------------------- """
## 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): ## iterate
""" iterating = True
runs the demo of the generalised Wiener filter while(iterating):
Parameters ## reconstruct map
---------- self.m = self.D(self.j, W=self.S, tol=1E-3, note=False)
space : space, *optional* if(self.m is None):
`space` can be any space from nifty, that supports the plotting break
routine (default: r1 = rg_space(512,1,zerocenter=False))
s2n : positive number, *optional* ## reconstruct power spectrum
`s2n` is the signal to noise (default: 3) tr_B1 = self.Sk.pseudo_tr(self.m) ## == Sk(m).pseudo_dot(m)
nvar : positive number, *optional* tr_B2 = self.Sk.pseudo_tr(self.D, loop=True)
the noise variance, `nvar` will be calculated according to
`s2n` if not specified (default: None) numerator = 2 * q + tr_B1 + abs(delta) * tr_B2 ## non-bare(!)
""" power = numerator / denominator
global s_space,k_space,d_space,power,S,Sk,R,N,Nj,D,s,n,d,j,m
## check convergence
## setting up signal, noise, data and the operators S, N and R dtau = log(power / self.S.get_power(), base=self.S.get_power())
setup(space,s2n=s2n,nvar=nvar) iterating = (np.max(np.abs(dtau)) > 2E-2)
print max(np.abs(dtau))
## information source
j = R.adjoint_times(N.inverse_times(d)) ## update signal covariance
self.S.set_power(power, bare=False) ## auto-updates D
## information propagator
D = propagator_operator(s_space,sym=True,imp=True,para=[S,N,R]) ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
## reconstructed map def plot(self):
m = D(j) """
if(m is None): Produces plots.
return None
"""
## fields ## plot signal
s.plot(title="signal",**kwargs) self.s.plot(title="signal")
# n.cast_domain(s_space,newtarget=k_space) ## plot data
# n.plot(title="noise",**kwargs) try:
# n.cast_domain(d_space,newtarget=d_space) d_ = field(self.z, val=self.d.val, target=self.k)
d.cast_domain(s_space,newtarget=k_space) d_.plot(title="data", vmin=self.s.min(), vmax=self.s.max())
d.plot(title="data",vmin=np.min(s.val),vmax=np.max(s.val),**kwargs) except:
d.cast_domain(d_space,newtarget=d_space) pass
m.plot(title="reconstructed map",vmin=np.min(s.val),vmax=np.max(s.val),**kwargs) ## plot map
if(self.m is None):
## power spectrum self.s.plot(power=True, mono=False, other=self.power)
# s.plot(title="power spectra",power=True,other=(m,power),mono=False) else:
self.m.plot(title="reconstructed map", vmin=self.s.min(), vmax=self.s.max())
## uncertainty self.m.plot(power=True, mono=False, other=(self.power, self.S.get_power()))
# 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)
##============================================================================= ##=============================================================================
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
def run_critical(space=r2,s2n=3,nvar=None,q=1E-12,alpha=1,perception=[1,0],**kwargs): if(__name__=="__main__"):
""" # pl.close("all")
runs the demo of the critical generalised Wiener filter
## define signal space
Parameters x_space = rg_space(128)
----------
space : space, *optional* ## setup problem
`space` can be any space from nifty, that supports the plotting p = problem(x_space, log=True)
routine (default: r2 = rg_space(64,2)) ## solve problem given some power spectrum
s2n : positive number, *optional* p.solve()
`s2n` is the signal to noise (default: 3) ## solve problem
nvar : positive number, *optional* p.solve_critical()
the noise variance, `nvar` will be calculated according to
`s2n` if not specified (default: None) p.plot()
q : positive number, *optional*
`q` is the minimal power on all scales (default: 1E-12) ## retrieve objects
alpha : a number >= 1, *optional* k_space = p.k
`alpha` = 1 means Jeffreys prior for the power spectrum (default: 1) power = p.power
perception : array of shape (2,1), *optional* S = p.S
perception[0] is delta, perception[1] is epsilon. They are tuning Sk = p.Sk
factors for the filter (default: [1,0]) s = p.s
R = p.R
See Also d_space = p.R.target
-------- N = p.N
infer_power Nj = p.Nj
d = p.d
""" j = p.j
global s_space,k_space,d_space,power,rho,S,Sk,R,N,Nj,D,s,n,d,j,m D = p.D
m = p.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")
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
...@@ -47,24 +47,11 @@ about.infos.off() ...@@ -47,24 +47,11 @@ about.infos.off()
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
## spaces # (global) Faraday map
h = hp_space(128) m = field(hp_space(128), val=np.load("demo_faraday_map.npy"))
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
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
##============================================================================= ##=============================================================================
def run(projection=False, power=False): def run(projection=False, power=False):
...@@ -74,47 +61,71 @@ def run(projection=False, power=False): ...@@ -74,47 +61,71 @@ def run(projection=False, power=False):
Parameters Parameters
---------- ----------
projection : bool, *optional* projection : bool, *optional*
Whether to additionaly include projections in the demo or not. If Whether to additionaly show projections or not (default: False).
``projection == True`` the projection operator `Sk` will be
defined. (default: False)
power : bool, *optional* power : bool, *optional*
Whether to additionaly show power spectra in the demo or not. Whether to additionaly show power spectra or not (default: False).
(default: False)
""" """
global Sk nicely = {"vmin":-4, "vmax":4, "cmap":ncmap.fm()}
## start in hp_space
# (a) representation on HEALPix grid
m0 = m m0 = m
m0.plot(title=r"$m$ on a HEALPix grid", **nicely)
nicely.update({"cmap":ncmap.fm()}) # healpy bug workaround
## transform to lm_space # (b) representation in spherical harmonics
m1 = m0.transform(l) 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): if(projection):
## define projection operator # define projection operator
Sk = projection_operator(l) Sk = projection_operator(m1.domain)
## project quadrupole # project quadrupole
m2 = Sk(m0, band=2) m2 = Sk(m0, band=2)
m2.plot(title=r"angular quadrupole of $m$ on a HEALPix grid", **nicely)
## transform to gl_space # (c) representation on Gauss-Legendre grid
m3 = m1.transform(g) 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 if(projection):
m4 = m1.transform(g_) ## auxiliary gl_space m4 = Sk(m3, band=2)
m4.cast_domain(r) ## rg_space cast m4.plot(title=r"angular quadrupole of $m$ on a Gauss-Legendre grid", **nicely)
m4.set_val(np.roll(m4.val[::-1, ::-1], g.nlon()//2, axis=1)) ## rearrange
if(power): # (d) representation on regular grid
## restrict to central window y_space = gl_space(384, nlon=768) # auxiliary gl_space
m5 = field(r_, val=m4[128:256, 256:512]).transform() 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): 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): if(projection):
m2.plot(title=r"quadrupole of $m$ on a HEALPix grid", vmin=-4, vmax=4, cmap=ncmap.fm()) m5.target.set_power_indices(log=False)
m3.plot(title=r"$m$ on a spherical Gauss-Legendre grid", vmin=-4, vmax=4, cmap=ncmap.fm()) # define projection operator
m4.plot(title=r"$m$ on a regular 2D grid", vmin=-4, vmax=4, cmap=ncmap.fm()) Sk = projection_operator(m5.target)
if(power): # project quadrupole
m5.plot(title=r"(restricted, binned) Fourier power spectrum of $m$", vmin=1E-3, vmax=1E+0, mono=False, log=True) 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)
##-----------------------------------------------------------------------------
...@@ -32,36 +32,39 @@ ...@@ -32,36 +32,39 @@
"""