#!/usr/bin/env python
# coding: utf-8
# # Bayesian signal reconstruction with Gaussian Random Fields (Wiener Filtering) - CMB example
# Florent Leclercq,
# Imperial Centre for Inference and Cosmology, Imperial College London,
# florent.leclercq@polytechnique.org
# This notebook requires the CLASS code (https://class-code.net) and its python wrapper classy, as well as the Planck colormap (https://raw.githubusercontent.com/zonca/paperplots/master/data/Planck_Parchment_RGB.txt).
# Optionally, pre-computed data are available:
# * http://www.florent-leclercq.eu/data/Bayes_InfoTheory/sqrtsignalcovarPix.npy (2.4 GB)
# * http://www.florent-leclercq.eu/data/Bayes_InfoTheory/sqrtCovWF.npy (2.4 GB)
# * http://www.florent-leclercq.eu/data/Bayes_InfoTheory/CovWFinvnoisecovarmat.npy (2.4 GB)
# * http://www.florent-leclercq.eu/data/Bayes_InfoTheory/invnoisecovar.npy (98.4 kB)
# In[1]:
import numpy as np
import scipy.linalg
import os.path
import matplotlib.pyplot as plt
from classy import Class
import healpy as hp
np.random.seed(123457)
get_ipython().run_line_magic('matplotlib', 'inline')
# In[2]:
# Download and define the Planck color map
from matplotlib.colors import ListedColormap
if not os.path.isfile("data/Planck_Parchment_RGB.txt"):
get_ipython().system('wget https://raw.githubusercontent.com/zonca/paperplots/master/data/Planck_Parchment_RGB.txt --directory-prefix=data/')
planck = ListedColormap(np.loadtxt("data/Planck_Parchment_RGB.txt")/255.)
planck.set_bad("gray") # color of missing pixels
planck.set_under("white") # color of background
# ## Setup Cls using CLASS
# In[3]:
# Define cosmology (what is not specified will be set to CLASS default parameters)
paramsLCDM = {
'h':0.702,
'n_s':0.9619,
'Omega_b':0.045,
'Omega_cdm':0.272-0.045,
'output':'tCl lCl',
'l_max_scalars': 1000,
'ic':'ad',
}
# Create an instance of the CLASS wrapper
cosmoLCDM = Class()
# Set the parameters to the cosmological code
cosmoLCDM.set(paramsLCDM)
# Run the whole code.
cosmoLCDM.compute()
# In[4]:
# Compute the power spectrum for the current set of cosmological parameters
res=cosmoLCDM.raw_cl(1000)
ell=res['ell']
Cl=res['tt']
# In[5]:
# Clean CLASS
cosmoLCDM.struct_cleanup()
# In[6]:
# Define a function of l from the arrays
from scipy.interpolate import InterpolatedUnivariateSpline
Clfunc = InterpolatedUnivariateSpline(ell, Cl, k=2)
# In[7]:
# Plot l*(l+1)*Cl/(2*pi)
plt.xlabel("$\ell$",size=18)
plt.ylabel("$\ell(\ell+1) C_\ell$",size=18)
plt.loglog(ell,Cl*(ell*(ell+1))/(2*np.pi),label='CLASS output')
plt.loglog(np.arange(0,999,2),Clfunc(np.arange(0,999,2))*(np.arange(0,999,2)*(np.arange(0,999,2)+1))/(2*np.pi)
,label='Spline function')
plt.legend(loc='best')
plt.show()
# In[8]:
# Setup notations
nside=32
npix=hp.pixelfunc.nside2npix(nside)
lmax=min(len(Cl)-1, 3*nside-1)
nalm=hp.Alm.getsize(lmax)
# Above $\mathrm{nside}=64$ the covariance matrix can't be stored...
# ## Setup signal covariance matrix
# In[9]:
# Setup covariance matrix in harmonic space
covarFourier=np.zeros(nalm,dtype=complex)
for l in range(lmax+1):
for m in range(lmax+1):
idx=hp.Alm.getidx(lmax,l,m)
covarFourier[idx]=Clfunc(l)
# In[10]:
# Plot diagonal of the harmonic-space covariance matrix
plt.xlabel("$a_{\ell m}$",size=18)
plt.ylabel("$C_{\ell}$",size=18)
plt.loglog(np.arange(nalm),covarFourier.real)
plt.show()
# Each row of the covariance matrix in pixel space is given by:
# $C_i = \mathcal{F}^{-1} \left[ C_{a_{\ell m}a_{\ell m}}a_{\ell m}(i) \right]$
# where
# $a_{\ell m}(i) = \mathcal{F} \left[ e(i) \right]$.
# $e(i) = [\delta^{i,j}]_{0 \leq j < N_\mathrm{pix}}^\mathrm{T}$ is the unit vector of the pixel space basis.
# $\mathcal{F}$ and $\mathcal{F}^{-1}$ are transformations from harmonic space to pixel space:
# \begin{eqnarray}
# m(n) & = & \sum_{\ell m} a_{\ell m} Y_{\ell m}(n)\\
# a_{\ell m} & = & \sum_n m(n) Y_{\ell m}^*(n).
# \end{eqnarray}
# In[11]:
get_ipython().run_cell_magic('capture', '', 'if os.path.isfile("data/sqrtsignalcovarPix.npy"):\n # Load precomputed sqrt covariance matrix in pixel space\n sqrtsignalcovarPix=np.load("data/sqrtsignalcovarPix.npy")\nelse:\n # Setup covariance matrix in pixel space\n covarFouriermat=np.diagflat(covarFourier)\n signalcovarPix=np.zeros((npix,npix))\n for i in xrange(npix):\n ei=np.zeros(npix)\n ei[i]=1.\n alm=hp.sphtfunc.map2alm(ei)\n Calm=covarFouriermat.dot(alm)\n signalcovarPix[i]=hp.sphtfunc.alm2map(Calm,nside)\n signalcovarPix=signalcovarPix.T\n sqrtsignalcovarPix=scipy.linalg.sqrtm(signalcovarPix)\n del covarFouriermat, signalcovarPix\n np.save("data/sqrtsignalcovarPix",signalcovarPix)\n')
# The signal covariance matrix is diagonal in harmonic space, but dense in pixel space...
# In[12]:
# Show sqrt covariance matrix in pixel space
i=1000
ei=np.zeros(npix)
ei[i]=1.
hp.mollview(ei, coord='G', title='one pixel', unit='mK',cmap="Blues")
hp.mollview(sqrtsignalcovarPix.real[i], coord='G', title='one row of the sqrt covariance matrix', unit='mK',cmap="Blues")
plt.show()
# ## Setup mask and noise covariance
# In[13]:
get_ipython().run_cell_magic('capture', '', '# Load the mask\nfn = "data/TMASK_32_R2.01_full.fits"\ntmask_map = hp.read_map(fn)\n# Simplify to binary\ntmask_map[np.where(tmask_map>0.)]=1.\n')
# In[14]:
hp.mollview(tmask_map, coord='G', title='mask', unit='mK', cmap="magma")
plt.show()
# In[15]:
get_ipython().run_cell_magic('capture', '', 'if os.path.isfile("data/invnoisecovar.npy"):\n # Load precomputed inverse noise covariance matrix in pixel space\n invnoisecovar=np.load("data/invnoisecovar.npy")\nelse:\n # Setup the noise covariance (infinite in masked regions)\n noisepower=1e-12\n noisecovar=noisepower*tmask_map\n noisecovar[np.where(tmask_map==0.)]=10000.\n invnoisecovar=1./noisecovar\n np.save("data/invnoisecovar",invnoisecovar)\n')
# The noise covariance matrix is assumed diagonal in pixel space.
# ## Generate mock data
# In[16]:
get_ipython().run_cell_magic('capture', '', '# Generate the true CMB signal\nsignal_map, signal_alms = hp.synfast(Cl, nside, alm=True, lmax=lmax, new=True)\n')
# In[17]:
hp.mollview(signal_map, coord='G', title='true signal', unit='mK', cmap=planck)
plt.show()
# Data model: $d=s+n$
# In[18]:
# Generate simulated data
normalsim = np.random.normal(0., 1., len(signal_map))
noise_map = normalsim/np.sqrt(invnoisecovar+1e-12)
data_map = signal_map + noise_map
data_map_vis = hp.ma(data_map)
data_map_vis.mask = 1-tmask_map
# In[19]:
hp.mollview(data_map_vis, coord='G', title='simulated data', unit='mK', cmap=planck)
plt.show()
# ## Setup Wiener filter
# Covariance of the Wiener Filter:
# \begin{equation}
# \mathrm{Cov}_\mathrm{WF} = (N^{-1}+S^{-1})^{-1} = S^{1/2}(I+S^{1/2}N^{-1}S^{1/2})^{-1}S^{1/2}
# \end{equation}
# For applications we only need to keep in memory
# $\mathrm{Cov}_\mathrm{WF}N^{-1}$ and $\sqrt{\mathrm{Cov}_\mathrm{WF}}$.
# In[20]:
get_ipython().run_cell_magic('capture', '', 'if os.path.isfile("data/CovWFinvnoisecovarmat.npy") and os.path.isfile("data/sqrtCovWF.npy"):\n # Load precomputed covariance of the Wiener filter\n del sqrtsignalcovarPix\n CovWFinvnoisecovarmat=np.load("data/CovWFinvnoisecovarmat.npy")\n sqrtCovWF=np.load("data/sqrtCovWF.npy")\nelse:\n # Setup covariance of the Wiener filter\n invnoisecovarmat=np.diagflat(tmask_map*invnoisecovar)\n M=np.identity(npix)+sqrtsignalcovarPix.dot(invnoisecovarmat).dot(sqrtsignalcovarPix)\n CovWF=sqrtsignalcovarPix.dot(np.linalg.inv(M)).dot(sqrtsignalcovarPix)\n del sqrtsignalcovarPix, M\n # Setup CovWF*N^-1\n CovWFinvnoisecovarmat=CovWF.dot(invnoisecovarmat)\n np.save("data/CovWFinvnoisecovarmat",CovWFinvnoisecovarmat)\n # Setup sqrtCovWF for simulating signals\n CovWFsym=(CovWF+CovWF.T)/2\n del CovWF\n sqrtCovWF=scipy.linalg.sqrtm(CovWFsym)\n del CovWFsim\n np.save("data/sqrtCovWF",sqrtCovWF)\n')
# ## Perform signal reconstruction (apply Wiener Filtering)
# Mean of the Wiener posterior:
# \begin{equation}
# s_\mathrm{WF} = \mathrm{Cov}_\mathrm{WF} N^{-1} d
# \end{equation}
# In[21]:
# Wiener filtered map (posterior mean)
sWF=CovWFinvnoisecovarmat.dot(data_map).real
# In[22]:
hp.mollview(sWF, coord='G', title='Wiener-filtered data', unit='mK', cmap=planck)
plt.show()
# ## Generate constrained realizations (draw samples from the Wiener posterior)
# Samples of the Wiener posterior:
# \begin{equation}
# s=s_\mathrm{WF}+\sqrt{C_\mathrm{WF}} \, G(0,1)
# \end{equation}
# so that $\left\langle s \right\rangle = s_\mathrm{WF}$ and $\mathrm{Cov}(s) = C_\mathrm{WF}$
# In[23]:
# Constrained realizations (samples of the Wiener posterior)
cr1=sqrtCovWF.dot(np.random.normal(0.,1.,npix)).real+sWF
cr2=sqrtCovWF.dot(np.random.normal(0.,1.,npix)).real+sWF
cr3=sqrtCovWF.dot(np.random.normal(0.,1.,npix)).real+sWF
# In[24]:
hp.mollview(cr1.real, coord='G', title='One simulated signal', unit='mK', cmap=planck)
hp.mollview(cr2.real, coord='G', title='One simulated signal', unit='mK', cmap=planck)
hp.mollview(cr3.real, coord='G', title='One simulated signal', unit='mK', cmap=planck)
plt.show()
# In[25]:
hp.mollview(cr1.real-sWF, coord='G', title='Simulated noise contribution', unit='mK', cmap=planck)
# In[26]:
hp.mollview(cr1.real-signal_map, coord='G', title='Reconstructed - true signal', unit='mK', cmap=planck)
# Of course, this is a toy example. At higher resolution, it is impossible to deal with (invert, or even store) dense covariance matrices in either pixel or harmonic space. Techniques have been devised to perform Wiener filtering in such situations: (preconditioned) conjugate gradients and messenger-field approaches. They are beyond the scope of these lectures.