Skip to content

Commit ec7ef35

Browse files
authored
Merge pull request #705 from erusseil/main
UV-extincted BB for Rainbow
1 parent 9345469 commit ec7ef35

5 files changed

Lines changed: 434 additions & 87 deletions

File tree

light-curve/light_curve/light_curve_py/features/rainbow/_base.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,8 @@
1515
__all__ = ["BaseRainbowFit"]
1616

1717

18-
# CODATA 2018, grab from astropy
19-
planck_constant = 6.62607004e-27 # erg s
20-
speed_of_light = 2.99792458e10 # cm/s
21-
boltzman_constant = 1.380649e-16 # erg/K
2218
sigma_sb = 5.6703744191844314e-05 # erg/(cm^2 s K^4)
19+
speed_of_light = 2.99792458e10 # cm/s
2320

2421

2522
IMINUIT_IMPORT_ERROR = (
@@ -69,6 +66,12 @@ def _temperature_parameter_names() -> List[str]:
6966
"""Temperature parameter names."""
7067
return NotImplementedError
7168

69+
@staticmethod
70+
@abstractmethod
71+
def _spectral_parameter_names() -> List[str]:
72+
"""Spectral model parameter names."""
73+
return NotImplementedError
74+
7275
def __post_init__(self) -> None:
7376
super().__post_init__()
7477

@@ -81,6 +84,7 @@ def __post_init__(self) -> None:
8184
common=self._common_parameter_names(),
8285
bol=self._bolometric_parameter_names(),
8386
temp=self._temperature_parameter_names(),
87+
spec=self._spectral_parameter_names(),
8488
bands=self.bands,
8589
with_baseline=self.with_baseline,
8690
)
@@ -207,14 +211,6 @@ def _unscale_covariance(self, cov, t_scaler: Scaler, m_scaler: MultiBandScaler)
207211
cov[:, i] *= scale
208212
cov[i, :] *= scale
209213

210-
@staticmethod
211-
def planck_nu(wave_cm, T):
212-
"""Planck function in frequency units."""
213-
nu = speed_of_light / wave_cm
214-
return (
215-
(2 * planck_constant / speed_of_light**2) * nu**3 / np.expm1(planck_constant * nu / (boltzman_constant * T))
216-
)
217-
218214
def _lsq_model_no_baseline(self, x, *params):
219215
"""Model function for the fit."""
220216
t, _band_idx, wave_cm = x
@@ -228,7 +224,8 @@ def _lsq_model_no_baseline(self, x, *params):
228224
# peak_nu = 2.821 * boltzman_constant * temp / planck_constant # Wien displacement law
229225
# norm = self.planck_nu(speed_of_light / peak_nu, temp) # Peak = 1 normalization
230226

231-
planck = self.planck_nu(wave_cm, temp) / norm
227+
spectral = self.spectral_func(wave_cm, temp, params)
228+
planck = spectral / norm
232229

233230
flux = planck * bol
234231

light-curve/light_curve/light_curve_py/features/rainbow/_parameters.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def create_parameters_class(
2929
common: List[str],
3030
bol: List[str],
3131
temp: List[str],
32+
spec: List[str],
3233
bands: Bands,
3334
with_baseline: bool,
3435
):
@@ -37,20 +38,22 @@ def create_parameters_class(
3738
Parameters
3839
----------
3940
cls_name : str
40-
Name of the class to create
41+
Name of the class to create.
4142
common : list of str
42-
Common parameters for both bolometric and temperature models
43+
Common parameters for both bolometric and temperature models.
4344
bol : list of str
44-
Bolometric model parameters, without common parameters
45+
Bolometric model parameters, without common parameters.
4546
temp : list of str
46-
Temperature model parameters, without common parameters
47+
Temperature model parameters, without common parameters.
48+
spec : list of str
49+
Spectral model parameters.
4750
bands : list of str
4851
Unique list of bands in the dataset. It is used to generate baseline
4952
parameters when `with_baseline` is True.
5053
with_baseline : bool
5154
Whether to include baseline parameters, one per band in `bands`.
5255
"""
53-
attributes = common + bol + temp
56+
attributes = common + bol + temp + spec
5457
if with_baseline:
5558
baseline = list(map(baseline_parameter_name, bands.names))
5659
attributes += baseline
@@ -61,14 +64,19 @@ def create_parameters_class(
6164
enum.common_idx = np.array([enum[attr] for attr in common])
6265

6366
enum.bol = bol
64-
enum.bol_idx = np.array([enum[attr] for attr in enum.bol])
67+
enum.bol_idx = np.array([enum[attr] for attr in enum.bol], dtype=np.int64)
6568
enum.all_bol = common + bol
66-
enum.all_bol_idx = np.array([enum[attr] for attr in enum.all_bol])
69+
enum.all_bol_idx = np.array([enum[attr] for attr in enum.all_bol], dtype=np.int64)
6770

6871
enum.temp = temp
69-
enum.temp_idx = np.array([enum[attr] for attr in enum.temp])
72+
enum.temp_idx = np.array([enum[attr] for attr in enum.temp], dtype=np.int64)
7073
enum.all_temp = common + temp
71-
enum.all_temp_idx = np.array([enum[attr] for attr in enum.all_temp])
74+
enum.all_temp_idx = np.array([enum[attr] for attr in enum.all_temp], dtype=np.int64)
75+
76+
enum.spec = spec
77+
enum.spec_idx = np.array([enum[attr] for attr in enum.spec], dtype=np.int64)
78+
enum.all_spec = spec
79+
enum.all_spec_idx = np.array([enum[attr] for attr in enum.all_spec], dtype=np.int64)
7280

7381
enum.with_baseline = with_baseline
7482
if with_baseline:

light-curve/light_curve/light_curve_py/features/rainbow/generic.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from light_curve.light_curve_py.features.rainbow._base import BaseRainbowFit
66

77
from .bolometric import BaseBolometricTerm, bolometric_terms
8+
from .spectral import BaseSpectralTerm, spectral_terms
89
from .temperature import BaseTemperatureTerm, temperature_terms
910

1011
__all__ = ["RainbowFit"]
@@ -59,16 +60,26 @@ class RainbowFit(BaseRainbowFit):
5960

6061
bolometric: Union[str, BaseBolometricTerm] = dataclass_field(default="bazin", kw_only=True)
6162
"""Which parametric bolometric term to use"""
63+
6264
temperature: Union[str, BaseTemperatureTerm] = dataclass_field(default="sigmoid", kw_only=True)
6365
"""Which parametric temperature term to use"""
6466

67+
spectral: Union[str, BaseSpectralTerm] = dataclass_field(
68+
default="planck",
69+
kw_only=True,
70+
)
71+
"""Which spectral term to use"""
72+
6573
def __post_init__(self):
6674
if not isinstance(self.bolometric, BaseBolometricTerm):
6775
self.bolometric = bolometric_terms[self.bolometric]
6876

6977
if not isinstance(self.temperature, BaseTemperatureTerm):
7078
self.temperature = temperature_terms[self.temperature]
7179

80+
if not isinstance(self.spectral, BaseSpectralTerm):
81+
self.spectral = spectral_terms[self.spectral]
82+
7283
super().__post_init__()
7384

7485
def _common_parameter_names(self) -> List[str]:
@@ -84,16 +95,22 @@ def _temperature_parameter_names(self) -> List[str]:
8495
temperature_parameters = self.temperature.parameter_names()
8596
return [i for i in temperature_parameters if i not in self._common_parameter_names()]
8697

98+
def _spectral_parameter_names(self) -> List[str]:
99+
return self.spectral.parameter_names()
100+
87101
def bol_func(self, t, params):
88102
return self.bolometric.value(t, *params[self.p.all_bol_idx])
89103

90104
def temp_func(self, t, params):
91105
return self.temperature.value(t, *params[self.p.all_temp_idx])
92106

107+
def spectral_func(self, wave_cm, T, params):
108+
return self.spectral.value(wave_cm, T, *params[self.p.all_spec_idx])
109+
93110
def _parameter_scalings(self) -> Dict[str, str]:
94111
rules = super()._parameter_scalings()
95112

96-
for term in [self.bolometric, self.temperature]:
113+
for term in [self.bolometric, self.temperature, self.spectral]:
97114
for name, scaling in zip(term.parameter_names(), term.parameter_scalings()):
98115
rules[name] = scaling
99116

@@ -102,12 +119,14 @@ def _parameter_scalings(self) -> Dict[str, str]:
102119
def _initial_guesses(self, t, m, sigma, band) -> Dict[str, float]:
103120
initial = self.bolometric.initial_guesses(t, m, sigma, band)
104121
initial.update(self.temperature.initial_guesses(t, m, sigma, band))
122+
initial.update(self.spectral.initial_guesses(t, m, sigma, band))
105123

106124
return initial
107125

108126
def _limits(self, t, m, sigma, band) -> Dict[str, Tuple[float, float]]:
109127
limits = self.bolometric.limits(t, m, sigma, band)
110128
limits.update(self.temperature.limits(t, m, sigma, band))
129+
limits.update(self.spectral.limits(t, m, sigma, band))
111130

112131
return limits
113132

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from abc import abstractmethod
2+
from dataclasses import dataclass
3+
from typing import Dict, List, Union
4+
5+
import numpy as np
6+
7+
__all__ = [
8+
"spectral_terms",
9+
"BaseSpectralTerm",
10+
"PlanckSpectralTerm",
11+
"BlanketedPlanckSpectralTerm",
12+
]
13+
14+
# CODATA 2018
15+
planck_constant = 6.62607004e-27 # erg s
16+
speed_of_light = 2.99792458e10 # cm/s
17+
boltzman_constant = 1.380649e-16 # erg/K
18+
19+
20+
@dataclass()
21+
class BaseSpectralTerm:
22+
"""Spectral term for Rainbow"""
23+
24+
@staticmethod
25+
@abstractmethod
26+
def parameter_names() -> List[str]:
27+
return NotImplementedError
28+
29+
@staticmethod
30+
@abstractmethod
31+
def parameter_scalings() -> List[Union[str, None]]:
32+
return NotImplementedError
33+
34+
@staticmethod
35+
@abstractmethod
36+
def value(wave_cm, T, *params):
37+
return NotImplementedError
38+
39+
@staticmethod
40+
@abstractmethod
41+
def initial_guesses(t, m, sigma, band) -> Dict[str, float]:
42+
return NotImplementedError
43+
44+
@staticmethod
45+
@abstractmethod
46+
def limits(t, m, sigma, band) -> Dict[str, float]:
47+
return NotImplementedError
48+
49+
50+
@dataclass()
51+
class PlanckSpectralTerm(BaseSpectralTerm):
52+
"""Standard blackbody spectrum"""
53+
54+
@staticmethod
55+
def parameter_names():
56+
return []
57+
58+
@staticmethod
59+
def parameter_scalings():
60+
return []
61+
62+
@staticmethod
63+
def value(wave_cm, T, *params):
64+
nu = speed_of_light / wave_cm
65+
66+
return (
67+
(2 * planck_constant / speed_of_light**2) * nu**3 / np.expm1(planck_constant * nu / (boltzman_constant * T))
68+
)
69+
70+
@staticmethod
71+
def initial_guesses(t, m, sigma, band):
72+
return {}
73+
74+
@staticmethod
75+
def limits(t, m, sigma, band):
76+
return {}
77+
78+
79+
@dataclass()
80+
class BlanketedPlanckSpectralTerm(BaseSpectralTerm):
81+
"""Blackbody spectrum with exponential blanketing"""
82+
83+
@staticmethod
84+
def parameter_names():
85+
return ["log_intensity", "lambda_scale"]
86+
87+
@staticmethod
88+
def parameter_scalings():
89+
return [None, None]
90+
91+
@staticmethod
92+
def value(wave_cm, T, log_intensity, lambda_scale):
93+
base = PlanckSpectralTerm.value(wave_cm, T)
94+
95+
# lambda_scale is expressed in Angstrom
96+
lambda_scale_cm = lambda_scale * 1e-8
97+
98+
tau = 10**log_intensity * np.exp(-wave_cm / lambda_scale_cm)
99+
100+
return base * np.exp(-tau)
101+
102+
@staticmethod
103+
def initial_guesses(t, m, sigma, band):
104+
return {
105+
"log_intensity": 1.5,
106+
"lambda_scale": 100.0,
107+
}
108+
109+
@staticmethod
110+
def limits(t, m, sigma, band):
111+
# lambda_scale and log_intensity are degenerate. Putting either to ~0 leads to a classical BB.
112+
# We prevent log_intensity to go to 0, because the blanketing is more physically realistic
113+
# at low lambda_scale high log_intensity rather than high lambda_scale low log_intensity.
114+
return {
115+
"log_intensity": (1.5, 6.0),
116+
"lambda_scale": (100.0, 2000.0),
117+
}
118+
119+
120+
spectral_terms = {
121+
"planck": PlanckSpectralTerm,
122+
"blanketed": BlanketedPlanckSpectralTerm,
123+
}

0 commit comments

Comments
 (0)