SED evaluation

[1]:
%load_ext autoreload
%autoreload 2
%matplotlib inline
[2]:
from scipy import constants
import pylab as pl
import numpy as np
from matplotlib import rcParams, cm
rcParams['figure.figsize'] = 12, 8

# Imports needed for component separation
from fgbuster import CMB, Dust, Synchrotron, AnalyticComponent, MixingMatrix

AnalyticComponenet

The component_model module collects classes for SED evaluation. It has mainly two classes:

  • Component, abstract class which defines the shared API

    • Evaluation of the SED as well as its first and second order derivatives with respect to the free parameters

    • Keep track of the free parameters

  • AnalyticComponent

    • Construct a Component from an analytic expression

We’ll now focus on the latter. An AnalyticComponent can be constructed from anything that can be parsed by sympy, which include all the most common mathematical functions. A variable called nu has to be used to expresses the frequency dependence.

Construction

As an example, consider the SED of a modified back body

[3]:
analytic_expr = ('(nu / nu0)**(1 + beta_d) *'
                 '(exp(nu0 / temp * h_over_k) -1)'
                 '/ (exp(nu / temp * h_over_k) - 1)'
                 )
[4]:
comp = AnalyticComponent(analytic_expr)

The free parameters are automatically detected

[5]:
comp.params
[5]:
['beta_d', 'h_over_k', 'nu0', 'temp']

As in this case, some of them are not really free but we did not want to clutter the expression by hardcoding the value. Their value can be specified at construction time.

[6]:
h_over_k_val = constants.h * 1e9 / constants.k  # Assumes frequencies in GHz
nu0_val = 353.

comp = AnalyticComponent(analytic_expr,  # Same as before
                         h_over_k=h_over_k_val, nu0=nu0_val)  # but now keyword arguments fix some variables

and they are no longer free parameters

[7]:
comp.params
[7]:
['beta_d', 'temp']

Evaluation

Efficient evaluators of the analytic expression are created at construction time. The expression can be evaluated both for a single value and for an array of values. (Note that the free parameters are passed in the same order they appear in comp.params)

[8]:
nu_scalar = 50
beta_scalar = 1.6
temp_scalar = 16.

comp.eval(nu_scalar, beta_scalar, temp_scalar)
[8]:
0.07227073743860096
[9]:
nu_vector = np.logspace(1, 3, 20)

sed = comp.eval(nu_vector, beta_scalar, temp_scalar)

print('Output shape is :', sed.shape)
Output shape is : (20,)
[10]:
_ = pl.loglog(nu_vector, sed)
../_images/examples_component_and_mixingmatrix_16_0.png

Also the free parameters can be arrays

[11]:
beta_vector = np.linspace(1.2, 2.2, 4)
temp_vector = np.linspace(15, 27, 4)

seds = comp.eval(nu_vector, beta_vector, temp_vector)

print('Output shape is :', seds.shape)
Output shape is : (4, 20)
[12]:
colors_map = cm.ScalarMappable()
colors_map.set_array(beta_vector)
colors_map.autoscale()
colors = lambda x: colors_map.to_rgba(x)
for sed, beta, temp in zip(seds, beta_vector, temp_vector):
    pl.loglog(nu_vector, sed, c=colors(beta), label='Temp: %.0f   Beta: %.1f' % (temp, beta))
_ = pl.legend()
../_images/examples_component_and_mixingmatrix_19_0.png

They can be anything that is bradcating-compatible

[13]:
sedss = comp.eval(nu_vector, beta_vector, temp_vector[:, np.newaxis])

print('Output shape is :', sedss.shape)
Output shape is : (4, 4, 20)
[14]:
linewidths = lambda x: (x - temp_vector.min()) / 4. + 1

# Legend kludge
for temp in temp_vector:
    pl.plot([353.]*2, [1, 1.001], c='k', lw=linewidths(temp),
              label='Temp: %.0f' % temp)
for beta in beta_vector:
    pl.plot([353.]*2, [1, 1.001], c=colors(beta), lw=2,
              label='Beta: %.1f' % beta)
# Plotting
for temp, seds in zip(temp_vector, sedss):
    for beta, sed in zip(beta_vector, seds):
        pl.loglog(nu_vector, sed,
                  c=colors(beta), lw=linewidths(temp))

_ = pl.legend()
../_images/examples_component_and_mixingmatrix_22_0.png

Evaluation is reasonably quick. For example, evaluating a pixel-dependent SED over 5 frequency bands at nside=1024 takes few seconds.

[15]:
comp.eval(nu_vector[::4], np.linspace(1., 2., 12*1024**2), 20.).shape
[15]:
(12582912, 5)

Derivatives

Another important feature of the AnalyticComponents is that the derivatives with respect to the parameters are computed analytically at construction time and evaluate with respect to all the parameters using

[16]:
diffs = comp.diff(nu_vector, 1.6, 20.)
[17]:
for diff, param in zip(diffs, comp.params):
    pl.semilogx(nu_vector, diff, label=param)
_ = pl.legend()
../_images/examples_component_and_mixingmatrix_27_0.png

Predefined Components

Popular analytic SEDs are ready to be used. For example, we construct the very same Component we have been using above with

[18]:
dust = Dust(353., units='K_RJ')
print(dust.params)
['beta_d', 'temp']
[19]:
np.allclose(comp.eval(nu_vector, beta_vector, temp_vector),
            dust.eval(nu_vector, beta_vector, temp_vector)
           )
[19]:
True

As for the components, parameters can be fixed at construction time

[20]:
dust_fixed_temp = Dust(353., temp=temp_scalar, units='K_RJ')
print(dust_fixed_temp.params)
['beta_d']
[21]:
np.allclose(comp.eval(nu_vector, beta_vector, temp_scalar),
            dust_fixed_temp.eval(nu_vector, beta_vector)
           )
[21]:
True

Mixing Matrix

The mixing matrix allows to collect components and evaluate them

[22]:
mixing_matrix = MixingMatrix(Dust(353.), CMB(), Synchrotron(70.))
[23]:
mixing_matrix.params
[23]:
['Dust.beta_d', 'Dust.temp', 'Synchrotron.beta_pl']
[24]:
mixing_matrix.components
[24]:
['Dust', 'CMB', 'Synchrotron']

As the Components, the SED and its derivative can be easily evaluated

[25]:
mm_val = mixing_matrix.eval(nu_vector, 1.6, 20., -3)

print('Output shape is :', mm_val.shape)
Output shape is : (20, 3)
[26]:
for sed, name in zip(mm_val.T, mixing_matrix.components):
    pl.loglog(nu_vector, sed, label=name)
_ = pl.legend()
../_images/examples_component_and_mixingmatrix_40_0.png
[27]:
mm_diffs = mixing_matrix.diff(nu_vector, 1.6, 20., -1)
[28]:
for mm_diff, param in zip(mm_diffs, mixing_matrix.params):
    plot = pl.loglog(nu_vector, mm_diff, label=param)
    pl.loglog(nu_vector, -mm_diff, ls='--', c=plot[-1].get_color())
_ = pl.legend()
../_images/examples_component_and_mixingmatrix_42_0.png

Note that only the column affected by the derivative is reported in the output.

[29]:
mm_diffs[0].shape
[29]:
(20, 1)

For each parameter, the index of the column affected can be retrieved from the comp_of_dB attribute.

[30]:
mixing_matrix.comp_of_dB
[30]:
[0, 0, 2]