"""
Spectral models used for fitting
"""
import numpy as np
from mpmath import gammainc
[docs]def gammainc_up(a,z):
"""Vectorised upper incomplete gamma function.
Taken from: https://stackoverflow.com/questions/10542780/incomplete-gamma-function-in-python
"""
return np.asarray([gammainc(a, zi, regularized=False)
for zi in z]).astype(float)
[docs]def simple_power_law(v, a, c, v0):
"""Simple power law:
.. math::
S_v = c \\left( \\frac{v}{v_0} \\right)^a
Parameters
----------
v : `list`
Frequency in Hz.
a : `float`
Spectral Index.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
return c*(v/v0)**a
[docs]def simple_power_law_integrate(vmin_vmax, a, c, v0):
"""The bandwith intergration correction for the
simple power law using direct intergration (:ref:`derivation <simple_power_law_integrate>`):
.. math::
S_v = \\frac{c({\\nu_\\text{max}}^{a+1} - {\\nu_\\text{min}}^{a+1})}{\\rm{BW}\,\\nu_0^a(a+1)}
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
a : `float`
Spectral Index.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
return c * (vmax**(a+1) - vmin**(a+1)) / ( (vmax - vmin) * v0**a * (a+1) )
[docs]def broken_power_law(v, vb, a1, a2, c, v0):
"""Broken power law:
.. math::
S_v = \\begin{cases}
c \\left( \\frac{v}{v0} \\right)^{a1} & \\mathrm{if}\\: v \\leq vb \\\\
c \\left( \\frac{v}{v0} \\right)^{a2} \\left( \\frac{vb}{v0} \\right)^{a1-a2} & \\mathrm{otherwise} \\\\
\\end{cases}
Parameters
----------
v : `list`
Frequency in Hz.
vb : `float`
The frequency of the break in Hz.
a1 : `float`
The spectral index before the break.
a2 : `float`
The spectral index after the break.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
x = v / v0
xb = vb / v0
y1 = c*x**a1
y2 = c*x**a2*(xb)**(a1-a2)
return np.where(x <= xb, y1, y2)
[docs]def broken_power_law_intergral(vmin_vmax, vb, a1, a2, c, v0):
"""The bandwith intergration correction for the
broken power law using direct intergration (see :ref:`derivation <broken_power_law_intergral>` for full equation):
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
vb : `float`
The frequency of the break in Hz.
a1 : `float`
The spectral index before the break.
a2 : `float`
The spectral index after the break.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
return np.select(
[
(vmin < vmax) & (vmax <= vb),
(vb <= vmin) & (vmin < vmax),
(vmin < vb) & (vb < vmax)
],
[
c * ( vmax**(a1+1) - vmin**(a1+1) ) / ( (vmax - vmin) * v0**a1 * (a1+1) ),
c * ( vmax**(a2+1) - vmin**(a2+1) ) / ( (vmax - vmin) * v0**a2 * (a2+1) ) * ( vb / v0 )**(a1-a2),
c * ( vmax**(a1+1) - vmin**(a1+1) ) / ( (vb - vmin) * v0**a1 * (a1+1) ) + c * ( vmax**(a2+1) - vmin**(a2+1) ) / ( (vmax - vb) * v0**a2 * (a2+1) ) * ( vb / v0 )**(a1-a2),
]
)
def double_broken_power_law(v, vb1, vb2, a1, a2, a3, c, v0):
x = v / v0
xb1 = vb1 / v0
xb2 = vb2 / v0
return np.piecewise(
x,
[
x <= xb1,
(x > xb1) & (x <= xb2),
x > xb2
],
[
lambda x: c*x**a1,
lambda x: c*x**a2*(xb1)**(a1-a2),
lambda x: c*x**a3*(xb1)**(a1-a2)*(xb2)**(a2-a3)
]
)
[docs]def log_parabolic_spectrum(v, a, b, c, v0):
"""Log-parabolic spectrum:
.. math::
\\log_{10} S_v = a \\left [ \\log_{10} \\left ( \\frac{v}{v0} \\right ) \\right]^2 +
b \\, \\log_{10} \\left ( \\frac{v}{v0} \\right ) + c
Parameters
----------
v : `list`
Frequency in Hz.
a : `float`
Curvature parameter.
b : `float`
The spectral index for :math:`a = 0`.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
x = np.log10( v / v0 )
return 10**(a*x**2 + b*x + c)
[docs]def high_frequency_cut_off_power_law(v, vc, a, c, v0):
"""High-frequency cut-off power law:
.. math::
S_v = c \\left( \\frac{v}{v0} \\right)^{a} \\left ( 1 - \\frac{v}{vc} \\right ),\\qquad v < vc
Parameters
----------
v : `list`
Frequency in Hz.
vc : `list`
Cut off frequency in Hz.
a : `float`
Spectral Index.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
x = v / v0
xc = vc / v0
y1 = c*x**a * ( 1 - x / xc )
y2 = 0.
return np.where(x < xc, y1, y2)
[docs]def high_frequency_cut_off_power_law_intergral(vmin_vmax, vc, a, c, v0):
"""The bandwith intergration correction for the
high-frequency cut-off power law using direct intergration (see :ref:`derivation <high_frequency_cut_off_power_law_intergral>` for full equation):
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
vc : `list`
Cut off frequency in Hz.
a : `float`
Spectral Index.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
BW = vmax - vmin
v = (vmin + vmax) / 2
y1 = c / ( BW * v0**a ) * ( (vmax**(a+1) - vmin**(a+1)) / (a+1) + (vmax**(a+2) - vmin**(a+2)) / (vc * (a+2)))
y2 = 0.
return np.where(v < vc, y1, y2)
[docs]def high_frequency_cut_off_power_law_taylor(vmin_vmax, vc, a, c, v0):
"""The bandwith intergration correction for the
high-frequency cut-off power law using Taylor series expansion (see :ref:`derivation <high_frequency_cut_off_power_law_taylor>` for full equation):
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
vc : `list`
Cut off frequency in Hz.
a : `float`
Spectral Index.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
BW = vmax - vmin
v = (vmin + vmax) / 2
s0 = high_frequency_cut_off_power_law(v, vc, a, c, v0)
s2 = ( (c*a) / v0**a ) * ( (a - 1)*v**(a-2) - ((a + 1)*v**(a-1))/vc )
s4 = ( (c*a*(a - 1)*(a - 2)) / v0**a ) * ( (a - 3)*v**(a-4) - ((a + 1)*v**(a-3))/vc )
s6 = ( (c*a*(a - 1)*(a - 2)*(a - 3)*(a - 4)) / v0**a ) * ( (a - 5)*v**(a-6) - ((a + 1)*v**(a-5))/vc )
sv = s0 + (s2*BW**2) / 12 + (s4*BW**4) / 80 + (s6*BW**6) /448
return np.where(v < vc, sv, 0)
[docs]def low_frequency_turn_over_power_law(v, vpeak, a, c, beta, v0):
"""Low-frequency turn-over power law:
.. math::
S_v = c \\left( \\frac{v}{v0} \\right)^{a} \\exp\\left [ \\frac{a}{\\beta} \\left( \\frac{v}{vc} \\right)^{-\\beta} \\right ]
Parameters
----------
v : `list`
Frequency in Hz.
vpeak : `list`
Peak/Turn-over frequency in Hz.
a : `float`
The spectral index.
c : `float`
Constant.
beta : `float`
The smoothness of the turn-over.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
x = v / v0
xpeak = v / vpeak
return c * x**a * np.exp( a / beta * xpeak**(-beta) )
[docs]def low_frequency_turn_over_power_law_intergral(vmin_vmax, vpeak, a, c, beta, v0):
"""The bandwith intergration correction for the
low-frequency turn-over power law using direct intergration (see :ref:`derivation <low_frequency_turn_over_power_law_intergral>` for full equation):
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
vpeak : `list`
Peak/Turn-over frequency in Hz.
a : `float`
The spectral index.
c : `float`
Constant.
beta : `float`
The smoothness of the turn-over.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
BW = vmax - vmin
Xmin = ( vmin / v0 )**a
Ymin = - ( a / beta ) * ( vmin / vpeak )**( -beta )
Xmax = ( vmax / v0 )**a
Ymax = - ( a / beta ) * ( vmax / vpeak )**( -beta )
Z = - ( a + 1 ) / beta
return ( c / (BW * beta) ) * ( (vmax * Xmax * Ymax**(-Z) * gammainc_up(Z,Ymax)) - (vmin * Xmin * Ymin**(-Z) * gammainc_up(Z,Ymin)) )
[docs]def low_frequency_turn_over_power_law_taylor(vmin_vmax, vpeak, a, c, beta, v0):
"""The bandwith intergration correction for the
low-frequency turn-over power law using Taylor series expansion (see :ref:`derivation <low_frequency_turn_over_power_law_taylor>` for full equation):
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
vpeak : `list`
Peak/Turn-over frequency in Hz.
a : `float`
The spectral index.
c : `float`
Constant.
beta : `float`
The smoothness of the turn-over.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
BW = vmax - vmin
v = (vmax + vmin) / 2
X = (v / vpeak)**beta
s0 = low_frequency_turn_over_power_law(v, vpeak, a, c, beta, v0)
s2 = (s0 * a) / (v**2 * X**2) * (X**2* (a - 1) + X*(-2*a + beta + 1) + a)
s4 = (s0 * a) / (v**4 * X**4) * ( \
X**4 * (a**3 - 6*a**2 + 11*a - 6) + \
X**3 * (-4*a**3 + 6*a**2*beta + 18*a**2 - 4*a*beta**2 - 18*a*beta - 22*a + beta**3 + 6*beta**2 + 11*beta + 6) + \
X**2*a*(6*a**2 - 12*a*beta - 18*a + 7*beta**2 + 18*beta + 11) + \
X*a**2*(-4*a + 6*beta + 6) + \
a**3\
)
s6 = (s0 * a) / (v**6 * X**6) * ( \
X**6 * (a**5 - 15*a**4 + 85*a**3 - 225*a**2 + 274*a - 120) + \
X**5 * (-6*a**5 + 15*a**4*beta + 75*a**4 - 20*a**3*beta**2 - 150*a**3*beta - 340*a**3 + 15*a**2*beta**3 + 150*a**2*beta**2 + 510*a**2*beta + 675*a**2 -\
6*a*beta**4 - 75*a*beta**3 - 340*a*beta**2 - 675*a*beta - 548*a + beta**5 + 15*beta**4 + 85*beta**3 + 225*beta**2 + 274*beta + 12) + \
X**4 * a * (15*a**4 - 60*a**3*beta - 150*a**3 + 105*a**2*beta**2 + 450*a**2*beta + 510*a**2 - \
90*a*beta**3 - 525*a*beta**2 - 1020*a *beta - 675*a + 31*beta**4 + 225*beta**3 + 595*beta**2 + 675*beta + 274) + \
X**3 * a**2 * (-20*a**3 + 90*a**2*beta + 150*a**2 - 150*a*beta**2 - 450*a*beta - 340*a + 90*beta**3 + 375*beta**2 + 510*beta + 225) + \
X**2 * a**3 * (15*a**2 - 60*a*beta - 75*a + 65*beta**2 + 150*beta + 85) + \
X * a**4 * (-6*a + 15*beta + 15) + a**5 \
)
return s0 + (s2*BW**2) / 12 + (s4*BW**4) / 80 + (s6*BW**6) /448
[docs]def double_turn_over_spectrum(v, vc, vpeak, a, beta, c, v0):
"""Double turn over spectrum (has a low-frequency turnover and a high-frequency cut-off):
.. math::
S_v = c \\left( \\frac{v}{v0} \\right)^{a} \\left ( 1 - \\frac{v}{vc} \\right ) \\exp\\left [ \\frac{a}{\\beta} \\left( \\frac{v}{vc} \\right)^{-\\beta} \\right ],\\qquad v < vc
Parameters
----------
v : `list`
Frequency in Hz.
vc : `list`
Cut off frequency in Hz.
vpeak : `list`
Peak/turn-over frequency in Hz.
a : `float`
Spectral Index.
beta : `float`
The smoothness of the turn-over.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
x = v / v0
xc = vc / v0
xpeak = v / vpeak
y1 = c*x**a * ( 1 - x / xc ) * np.exp( a / beta * xpeak**(-beta) )
y2 = 0.
return np.where(x < xc, y1, y2)
[docs]def double_turn_over_spectrum_intergral(vmin_vmax, vc, vpeak, a, beta, c, v0):
"""The bandwith intergration correction for the
double turn over spectrum (has a low-frequency turnover and a high-frequency cut-off)
using direct intergration (see :ref:`derivation <double_turn_over_spectrum_intergral>` for full equation):
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
vc : `list`
Cut off frequency in Hz.
vpeak : `list`
Peak/turn-over frequency in Hz.
a : `float`
Spectral Index.
beta : `float`
The smoothness of the turn-over.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
BW = vmax - vmin
v = vmin + BW / 2
Xmin = ( vmin / v0 )**a
Xmax = ( vmax / v0 )**a
dXmin = ( vmin / v0 )**(a + 1)
dXmax = ( vmax / v0 )**(a + 1)
Ymin = - ( a / beta ) * ( vmin / vpeak )**( -beta )
Ymax = - ( a / beta ) * ( vmax / vpeak )**( -beta )
Z = - ( a + 1 ) / beta
dZ = - ( a + 2 ) / beta
part1 = vmax * Xmax * Ymax**(-Z) * gammainc_up(Z, Ymax) - vmin * Xmin * Ymin**(-Z) * gammainc_up(Z, Ymin)
part2 = ( v0 / vc ) * ( vmax * dXmax * Ymax**(-dZ) * gammainc_up(dZ, Ymax) - vmin * dXmin * Ymin**(-dZ) * gammainc_up(dZ, Ymin) )
y1 = ( c / ( BW * beta ) ) * (part1 - part2)
y2 = 0
return np.where(v < vc, y1, y2)
[docs]def double_turn_over_spectrum_taylor(vmin_vmax, vc, vpeak, a, beta, c, v0):
"""The bandwith intergration correction for the
double turn over spectrum (has a low-frequency turnover and a high-frequency cut-off)
using Taylor series expansion (see :ref:`derivation <double_turn_over_spectrum_taylor>` for full equation):
Parameters
----------
vmin_vmax : `tuple` (vmin, vmax)
Where vmin is the minimum and vmax is the maximum frequency
in Hz for each flux density measurement's bandwidth.
vc : `list`
Cut off frequency in Hz.
vpeak : `list`
Peak/turn-over frequency in Hz.
a : `float`
Spectral Index.
beta : `float`
The smoothness of the turn-over.
c : `float`
Constant.
v0 : `float`
Reference frequency.
Returns
-------
S_v : `list`
The flux density predicted by the model.
"""
vmin, vmax = vmin_vmax
BW = vmax - vmin
v = (vmax + vmin) / 2
X = (v / vpeak)**beta
Y = (v - vc)
Z = c*(v/v0)**a * np.exp( a / beta * (v/vpeak)**(-beta) )
s0 = double_turn_over_spectrum(v, vc, vpeak, a, beta, c, v0)
s2 = (Z * a) / (v**2 * vc * X**2) * (-a*Y - 2*v*X**2 + 2*v*X + X**2*Y*(1-a) + X*Y*(2*a - beta - 1))
s4 = (Z * a) / (v**4 * vc * X**4) * ( \
X**4 * (\
v * (-a**3 + 2*a**2 + a - 2) + \
vc * (a**3 - 6*a**2 + 11*a - 6) \
) + \
X**3 * (\
v * (4*a**3 - 6*a**2*beta - 6*a**2 + 4*a*beta**2 + 6*a*beta - 2*a - beta**3 - 2*beta**2 + beta + 2) + \
vc * (-4*a**3 + 6*a**2*beta + 18*a**2 - 4*a*beta**2 - 18*a*beta - 22*a + beta**3 + 6*beta**2 + 11*beta + 6) \
) + \
X**2*a*(\
v * (-6*a**2 + 12*a*beta + 6*a - 7*beta**2 - 6*beta + 1) + \
vc * (6*a**2 - 12*a*beta - 18*a + 7*beta**2 + 18*beta + 11) \
) + \
X*a**2*(\
v * (4*a - 6*beta - 2) + \
vc * (-4*a + 6*beta + 6) \
) + \
a**3*vc - a**3*v\
)
sv = s0 + (s2*BW**2) / 12 + (s4*BW**4) / 80
return np.where(v < vc, sv, 0)
[docs]def model_settings(print_models=False):
"""Holds metadata about spectral models such as common names and default fit parameters.
Parameters
----------
print_models : `boolean`, optional
If true, will print the models dictionary which is useful for debuging new models. Default False.
Returns
-------
model_dict : `dict`
Returns a dictionary in the format
{model_name: [model_function, short_name, start_params, mod_limits]}
"""
# fit starting value, min and max
# constant
c_s = 1.
c_min = 0.
c_max = None
# spectral index
a_s = -1.6
a_min = -8.
a_max = 3.
# Beta, he smoothness of the turn-over
beta_s = 1.
beta_min = 0.1
beta_max = 2.1
# High frequency cut off frequency
vc_s = 4e9
vc_both = None # will set the cut off frequency based on the data set's frequency range
# Lof frequency turn over frequency peak
vpeak_s = 100e6
vpeak_min = 10e6
vpeak_max = 2e9
model_dict = {
# Name: [model_function, short_name, start_params, mod_limits]
"simple_power_law" : [
simple_power_law,
"simple pl",
# (a, c)
(a_s, c_s),
[(a_min, a_max), (c_min, c_max)],
simple_power_law_integrate,
],
"broken_power_law" : [
broken_power_law,
"broken pl",
#(vb, a1, a2, c)
(1e9, a_s, a_s, c_s),
[(50e6, 5e9), (a_min, a_max), (a_min, a_max), (c_min, c_max)],
broken_power_law_intergral,
],
# "log_parabolic_spectrum" : [
# log_parabolic_spectrum,
# "lps",
# #(a, b, c)
# (-1, -1., c_s),
# [(-5, 2), (-5, 2), (None, c_max)],
# ],
"high_frequency_cut_off_power_law" : [
high_frequency_cut_off_power_law,
"pl hard cut-off",
#(vc, a, c)
(vc_s, a_s, c_s),
[vc_both, (a_min, 0.), (c_min, c_max)],
high_frequency_cut_off_power_law_taylor,
],
"low_frequency_turn_over_power_law" : [
low_frequency_turn_over_power_law,
"pl low turn-over",
#(vpeak, a, c, beta)
(vpeak_s, a_s, c_s, beta_s),
[(vpeak_min, vpeak_max), (a_min, 0.), (c_min, c_max) , (beta_min, beta_max)],
low_frequency_turn_over_power_law_taylor,
],
"double_turn_over_spectrum" : [
double_turn_over_spectrum,
"double turn over spectrum",
#(vc, vpeak, a, beta, c)
(vc_s, vpeak_s, a_s, beta_s, c_s),
[(vc_both), (vpeak_min, vpeak_max), (a_min, 0.), (beta_min, beta_max), (c_min, c_max)],
double_turn_over_spectrum_taylor,
],
#"double_broken_power_law" : [
# double_broken_power_law,
# "double bpl",
# (100e6, 1e9, -1.6, -1.6, -1.6, 0.1),
# [(10e6, 100e9), (1e9, 100e9), (-5, 5), (-5, 5), (-5, 5), (0, None)],
#],
}
if print_models:
# Print the models dictionary which is useful for debuging new models
for mod in model_dict.keys():
print(f"\n{mod}")
model_function, short_name, start_params, mod_limits, model_function_integrate = model_dict[mod]
print(f" model_function: {model_function.__name__}")
print(f" model_function_integrate: {model_function_integrate.__name__}")
print(f" short_name: {short_name}")
print(f" start_params: {start_params}")
print(f" mod_limits: {mod_limits}")
return model_dict