from typing import Optional
import numpy as np
import pandas as pd
from casper.utils.logger_config import setup_logger
logger = setup_logger(__name__)
[docs]
def Hernandez(JK: float, FEH: float = -2.5, CLASS: Optional[str] = None) -> float:
"""
Compute effective temperature (Teff) using the Hernandez Calibration.
This function estimates stellar effective temperature from the (J-Ks) color and
metallicity [Fe/H], using separate calibrations for GIANT and DWARF stars.
Parameters
----------
JK : float
The (J-Ks) color index.
FEH : float, optional
Metallicity [Fe/H] of the star. Default is -2.5.
CLASS : str, optional
Stellar class: "GIANT" or "DWARF". If None or unrecognized, defaults to "GIANT".
Returns
-------
float
Effective temperature in Kelvin. Returns np.nan if JK is out of the valid range.
Notes
-----
Based on the infrared flux method from [Hernandez et al. (2009)](https://ui.adsabs.harvard.edu/abs/2009A%26A...497..497G/abstract).
Reference: J-Ks in Table 5 and Eq. (10)
"""
if CLASS == "GIANT":
logger.info("Using GIANT calibration in Hernandez")
A0 = [0.6517, 0.6312, 0.0168, -0.0381, 0.0256, 0.0013]
elif CLASS == "DWARF":
logger.info("Using DWARF calibration in Hernandez")
A0 = [0.6524, 0.5813, 0.1225, -0.0646, 0.0370, 0.0016]
else:
logger.warning(f"Can't handle input class: {CLASS}. Defaulting to GIANT.")
A0 = [0.6517, 0.6312, 0.0168, -0.0381, 0.0256, 0.0013]
if JK >= 0.1 and JK <= 0.90:
Teff = (
A0[0]
+ (JK * A0[1])
+ (A0[2] * np.power(JK, 2))
+ (A0[3] * JK * FEH)
+ A0[4] * FEH
+ A0[5] * np.power(FEH, 2)
)
T_JK = 5040.0 / Teff
else:
T_JK = np.nan
return T_JK
[docs]
def Casagrande(JK: float, FEH: float = -2.5, CLASS: Optional[str] = None) -> float:
"""
Estimate effective temperature using the Casagrande Calibration.
This function calculates stellar effective temperature (Teff) from the J-Ks
color index and metallicity [Fe/H], using an empirical formula.
It is valid for 0.07 ≤ JK ≤ 0.80.
Parameters
----------
JK : float
The J-Ks color index.
FEH : float, optional
Metallicity [Fe/H] of the star. Default is -2.5.
CLASS : str, optional
Currently unused. Included for interface compatibility with Hernandez().
Returns
-------
float
Effective temperature in Kelvin. Returns np.nan if JK is out of bounds.
Notes
-----
Based on the calibration from [Casagrande et al. (2010)](https://ui.adsabs.harvard.edu/abs/2010A%26A...512A..54C/abstract).
Reference: J-Ks in Table 4 and Eq. (3)
"""
if JK >= 0.07 and JK <= 0.80:
Teff = (
0.6393
+ (JK * 0.6104)
+ (0.0920 * np.power(JK, 2))
+ (-0.0330 * JK * FEH)
+ (0.0291 * FEH)
+ (0.0020 * np.power(FEH, 2))
)
T_JK = 5040.0 / Teff
else:
T_JK = np.nan
logger.warning("Casagrande Calibration out of bounds")
return T_JK
[docs]
def Bergeat(JK: float) -> float:
"""
Estimate effective temperature using the Bergeat calibration.
This function calculates Teff using the $(J-K)_{0}$ color and [Fe/H], following the approach from Bergeat et al.
Parameters
----------
JK : float
The $(J-K)_{0}$ color index.
Returns
-------
float
Effective temperature in Kelvin.
Notes
-----
Based on the calibration from Bergeat et al. (2001), Eq.(19) and Table 5.
"""
CIj0 = JK
if CIj0 <= 2.1:
logT_JK = -0.184 * CIj0 + 3.74
elif CIj0 > 2.1:
logT_JK = -0.109 * CIj0 + 3.59
return np.power(10, logT_JK)
[docs]
def Fukugita(gr: float) -> float:
"""
Estimate effective temperature from g-r color index
using the Fukugita et al. (2011) relation.
Parameters
----------
gr : float
The g-r color index.
Returns
-------
float
Estimated effective temperature in Kelvin.
Returns np.nan if input is invalid.
"""
try:
return 1.09 * 10000 / (gr + 1.47)
except:
logger.warning("Skipping (g-r)")
return np.nan
[docs]
def determine_effective(TEMP_FRAME: pd.DataFrame) -> pd.DataFrame:
"""
Determine the adopted effective temperature from a table of temperature estimates.
This function:
- Sorts the input DataFrame by the "VALUE" column
- Filters out non-finite values
- Selects the median finite value as the adopted effective temperature
- Appends this value to the DataFrame with the index "ADOPTED"
Parameters
----------
TEMP_FRAME : pd.DataFrame
DataFrame containing a "VALUE" column with Teff estimates.
Returns
-------
pd.DataFrame
The original DataFrame with an additional row indexed as "ADOPTED",
containing the median finite temperature value.
Raises
------
AssertionError
If the selected temperature value is not finite.
"""
logger.info("Setting effective photometric temperature:")
TEMP_FRAME = TEMP_FRAME.sort_values(by=["VALUE"])
FINITE_FRAME = TEMP_FRAME[np.isfinite(TEMP_FRAME["VALUE"])]
INDEX = int(len(FINITE_FRAME) / 2)
value = float(FINITE_FRAME.iloc[INDEX]["VALUE"])
assert np.isfinite(value), "\t\t ERROR, PHOTO TEMP NOT FINITE"
TEMP_FRAME = pd.concat([TEMP_FRAME, pd.DataFrame(data=[value], columns=["VALUE"], index=["ADOPTED"])])
return TEMP_FRAME
[docs]
def calibrate_temp_frame(JK: float, gr: float, FEH: float = -2.5, CLASS: Optional[str] = None) -> pd.DataFrame:
"""
Build a DataFrame of calibrated effective temperature estimates using multiple color indices.
This function runs various photometric temperature calibrations (Casagrande, Hernandez,
Bergeat, Fukugita) depending on the availability of valid (finite) input values for
J-Ks and g-r color indices. It then attempts to determine an adopted Teff value.
Parameters
----------
JK : float
J-Ks color index.
gr : float
g-r color index.
FEH : float, optional
Metallicity [Fe/H]. Default is -2.5.
CLASS : str, optional
Stellar class, e.g., "GIANT" or "DWARF". Default is None.
Returns
-------
pd.DataFrame
A DataFrame containing the Teff estimates from each calibration
and an additional row "ADOPTED" with the median effective temperature.
If adoption fails, "ADOPTED" will be set to NaN.
"""
logger.info("Calibrating temperature frame")
if np.isfinite(JK):
TEMP_DICT = {
"Casagrande": Casagrande(JK, FEH, CLASS),
"Hernandez": Hernandez(JK, FEH, CLASS),
"Bergeat": Bergeat(JK),
}
else:
TEMP_DICT = {"Casagrande": np.nan, "Hernandez": np.nan, "Bergeat": np.nan}
if np.isfinite(gr):
TEMP_DICT["Fukugita"] = Fukugita(gr)
else:
TEMP_DICT["Fukugita"] = np.nan
TEMP_FRAME = pd.DataFrame(data=list(TEMP_DICT.values()), columns=["VALUE"], index=TEMP_DICT.keys())
try:
TEMP_FRAME = determine_effective(TEMP_FRAME)
except:
TEMP_FRAME.loc["ADOPTED"] = np.nan
return TEMP_FRAME