Source code for special.spec_indices

#! /usr/bin/env python

"""
Module with utilities to estimate the spectral type and gravity of an object 
based on spectral indices.

.. [GOR03]
   | Gorlova et al. 2003
   | **Gravity Indicators in the Near-Infrared Spectra of Brown Dwarfs**
   | *The Astrophysical Journal, Volume 593, Issue 1, pp. 1074-1092*
   | `https://arxiv.org/abs/astro-ph/0305147
     <https://arxiv.org/abs/astro-ph/0305147>`_
     
.. [SLE04]
   | Slesnick et al. 2004
   | **The Spectroscopically Determined Substellar Mass Function of the Orion 
     Nebula Cluster**
   | *The Astrophysical Journal, Volume 610, Issue 1, pp. 1045-1063*
   | `https://arxiv.org/abs/astro-ph/0404292
     <https://arxiv.org/abs/astro-ph/0404292>`_
          
.. [ALL07]
   | Allers et al. 2007
   | **Characterizing Young Brown Dwarfs Using Low-Resolution Near-Infrared 
     Spectra**
   | *The Astrophysical Journal, Volume 657, Issue 1, pp. 511-520*
   | `https://arxiv.org/abs/astro-ph/0611408
     <https://arxiv.org/abs/astro-ph/0611408>`_          
"""

__author__ = 'V. Christiaens'
__all__ = ['spectral_idx',
           'sp_idx_to_spt',
           'sp_idx_to_gravity',
           'digit_to_spt',
           'spt_to_digit']

import numpy as np
from .utils_spec import find_nearest

[docs]def spectral_idx(lbda, spec, band='H2O-1.5', spec_err=None, verbose=False): """ Computes a spectral index. Implemented so far: - the Na 1.1 mu index [ALL07]_ - the H2O 1.3 mu index [GOR03]_ - the H2O 1.5 mu index [ALL07]_ - the H2O 2 index [SLE04]_ - the CO 2.3 index [GOR03]_. Parameters ---------- lbda : numpy ndarray 1d numpy array containing the wavelengths of the spectrum in microns. spec : numpy ndarray 1d numpy array containing the measured flux (arbitrary units accepted). band: str, optional {'H2O-1.5', 'H2O-1.3', 'H2O-2', 'Na-1.1', 'CO-2.3'} Name of the band where the spectral index is defined (spectral feature + wavelength in mu) spec_err: numpy ndarray, optional 1d numpy array containing the uncertainties on the measured flux (arbitrary units accepted). If provided the uncertainty on the spectral index will also be returned. verbose: bool, optional Whther to print more information. Returns ------- index : float Value of the spectral index index_err: float [if spec_err is provided] Uncertainty on the spectral index. """ if band == 'Na-1.1': lbda_num0 = 1.15 lbda_num1 = 1.16 lbda_den0 = 1.134 lbda_den1 = 1.144 elif band == 'H2O-1.3': lbda_num0 = 1.334 lbda_num1 = 1.338 lbda_den0 = 1.32 lbda_den1 = 1.324 elif band == 'H2O-1.5': lbda_num0 = 1.55 lbda_num1 = 1.56 lbda_den0 = 1.492 lbda_den1 = 1.502 elif band == 'H2O-2': lbda_num0 = 2.035 lbda_num1 = 2.045 lbda_den0 = 2.145 lbda_den1 = 2.155 elif band == 'CO-2.3': lbda_num0 = 2.296 lbda_num1 = 2.298 lbda_den0 = 2.286 lbda_den1 = 2.288 else: raise ValueError("Band not recognised") idx_num0 = find_nearest(lbda, lbda_num0) idx_num1 = find_nearest(lbda, lbda_num1) # set floor/ceil constraints if the indices are the same if idx_num0 == idx_num1: idx_num0 = find_nearest(lbda, lbda_num0, constraint='floor') idx_num1 = find_nearest(lbda, lbda_num0, constraint='ceil') idx_den0 = find_nearest(lbda, lbda_den0) idx_den1 = find_nearest(lbda, lbda_den1) # set floor/ceil constraints if the indices are the same if idx_den0 == idx_den1: idx_den0 = find_nearest(lbda, lbda_den0, constraint='floor') idx_den1 = find_nearest(lbda, lbda_den1, constraint='ceil') raise_flag = False raise_flag2 = False # Check if enough spectral resolution (<5nm) thr = 0.005*lbda_num0/1.15 cond1 = abs(lbda[idx_num1+1] - lbda[idx_num1])<thr cond2 = abs(lbda[idx_den0+1] - lbda[idx_den0])<thr if cond1 or cond2: raise_flag = True # Check if overlap err_msg = "Indices overlap for num and den: not enough spectral resolution" if idx_den0 == idx_num1: raise_flag2 = True if abs(lbda[idx_den0]-lbda_den0)<abs(lbda[idx_num1]-lbda_num1): idx_num1 -= 1 if idx_num0 == idx_num1+1: raise ValueError(err_msg) else: idx_den0 += 1 if idx_den0 == idx_den1+1: raise ValueError(err_msg) if idx_den1 == idx_num0: raise_flag2 = True if abs(lbda[idx_den1]-lbda_den1)<abs(lbda[idx_num0]-lbda_num0): idx_num0 += 1 if idx_num0 == idx_num1+1: raise ValueError(err_msg) else: idx_den1 -= 1 if idx_den0 == idx_den1+1: raise ValueError(err_msg) if verbose: if raise_flag: msg = "*WARNING*: input spectrum has low spectral resolution." msg += "These spectral indices have not been tested or calibrated " msg += "at such a low spectral resolution (see Allers et al. 2007 " msg += "for details). Consider results with CAUTION." print(msg) if raise_flag2: msg = "*WARNING*: input spectrum has VERY low spectral resolution." msg += "Closest (non-overlapping) wavelengths to {:.3f}, {:.3f}, {:.3f} and" msg += " {:.3f} mu correspond to {:.3f}, {:.3f}, {:.3f} and {:.3f} mu resp." msg += "Consider results with EXTRA CAUTION." print(msg.format(lbda_num0, lbda_num1, lbda_den0, lbda_den1, lbda[idx_num0], lbda[idx_num1], lbda[idx_den0], lbda[idx_den1])) num = np.mean(spec[idx_num0:idx_num1+1]) den = np.mean(spec[idx_den0:idx_den1+1]) index = num/den if spec_err is None: return index else: num_err = np.sum(np.power(spec_err[idx_num0:idx_num1+1]/((idx_num1+1-idx_num0)*den),2)) den_err = np.sum(np.power(spec_err[idx_num0:idx_num1+1]*(idx_den1+1-idx_den0)*num/(den**2),2)) index_err = np.sqrt(num_err**2+den_err**2) return index, index_err
[docs]def sp_idx_to_spt(idx, name='H2O-1.5', idx_err=None, young=True): """ Estimates a spectral type from a spectral index. Implemented so far: - the H2O 1.3 mu index [GOR03]_ - the H2O 1.5 mu index [ALL07]_ - the H2O-2 index [SLE04]_ Note on scale of SpT: 0 = M0, 10 = L0. Parameters ---------- idx : float Value of spectral index name: str, optional {'H2O-1.3', 'H2O-1.5, 'H2O-2'} The name of the spectral index. idx_err: float, optional Uncertainty on the spectral index value young: bool, opt Whether the object is likely young (only used for 'H2O-1.3' index, which is gravity dependent) Returns ------- spt : float Value of the spectral type spt_err: float [if idx_err is provided] Uncertainty on the spectral type. """ if name == 'H2O-1.3': if young: a = 10.99 da = 6.06 b = 17.45 db = 7.68 else: a = 21.23 da = 7.82 b = 29.11 db = 9.56 spt = a - b*idx tot_err = da**2 # err related to a tot_err += np.power(db*idx,2) # err related to b if idx_err is not None: tot_err += np.power(idx_err*b, 2) spt_err = np.sqrt(tot_err) elif name == 'H2O-1.5': a = 0.75 da = 0.03 b = 0.044 db = 0.004 spt = (idx-a)/b tot_err = np.power(da/b,2) # err related to a tot_err += np.power(db*(idx-a)/b**2,2) # err related to b if idx_err is not None: tot_err += np.power(idx_err/b, 2) spt_err = np.sqrt(tot_err) elif name == 'H2O-2': a = 34.13 da = 1.19 b = 27.10 db = 1.20 spt = a - b*idx tot_err = da**2 # err related to a tot_err += np.power(db*idx,2) # err related to b if idx_err is not None: tot_err += np.power(idx_err*b, 2) spt_err = np.sqrt(tot_err) else: raise ValueError("not implemented for this line") return spt, spt_err
[docs]def sp_idx_to_gravity(idx, name='Na-1.1'): """ Provides a qualitative estimate of the gravity/youth based on a gravity-sensitive spectral index. Implemented so far: - the Na-1.1 index [ALL07]_ - the CO-2.3 index [GOR03]_ Parameters ---------- idx : float Value of spectral index name: str, optional {'Na-1.1', 'CO-2.3'} The name of the spectral index. """ if name == 'Na-1.1': if idx[0] < 1.4: msg = "The object's gravity is consistent with being very young " msg+= "(Na index less than 1.4; Allers et al. 2007))" else: msg= "The Na index does not suggest a very low gravity (i.e. it is" msg+= " unlikely to be very young; Allers et al. 2007))" elif name == 'CO-2.3': if idx[0] < 0.81: msg = "The CO-2.3 index is consistent with either a red giant or " msg += " a field L-dwarf with log(g) > 4.5 (CO-2.3 index less than" msg += " 0.88; Gorlova et al. 2003)" elif idx[0] < 0.88: msg = "The object's gravity is likely higher than 4.5, with a SpT " msg += "earlier than L0 (CO-2.3 index less than 0.88; Gorlova et " msg += "al. 2003)" else: msg= "The object is likely young, with a log(g) lower than 4.5 " msg+= "(CO-2.3 index larger than 0.88; Gorlova et al. 2003)" print(msg)
[docs]def spt_to_digit(spt, convention='splat'): """ Converts a string representing spectral type into an integer index. Parameters ---------- spt : str String representing the spectral index convention: str, optional {'splat', 'Allers+07'} Which convention to use to convert digit into spectral type. Convention from splat: K0 = 0, M0=10, L0=20, T0=30, Y9 = 49 Convention from [ALL07]_: M0 = 0, L0 = 10, ... Returns ------- idx : float or int Index value of the spectral type """ spt_str = spt.split()[0] sub = 0 if convention == 'Allers+07': sub=10 if 'K' in spt_str: idx = 0-sub loc = spt_str.find('K') if convention == 'Allers+07': msg="WARNING: Allers+07 convention doesn't handle SpT earlier than M" print(msg) elif 'M' in spt_str: idx = 10-sub loc = spt_str.find('M') elif 'L' in spt_str: idx = 20-sub loc = spt_str.find('L') elif 'T' in spt_str: idx = 30-sub loc = spt_str.find('T') elif 'Y' in spt_str: idx = 40-sub loc = spt_str.find('Y') else: idx = np.nan print("WARNING: Spectral type not between K0 and Y9. Idx set to NaN.") if len(spt)>1: if len(spt[loc+1:]) > 2: if spt[loc+2] == ".": idx+=float(spt[loc+1:loc+4]) elif spt[loc+1].isdigit(): idx+=float(spt[loc+1]) else: idx+=5 # in case just "M" or "L" is given, let's assume it's M5 or L5 return idx
[docs]def digit_to_spt(idx, convention='splat'): """ Converts an integer index into spectral type. Parameters ---------- idx : float or int Index value of the spectral type convention: str, optional {'splat', 'Allers+07'} Which convention to use to convert digit into spectral type. Convention from splat: K0 = 0, M0=10, L0=20, T0=30, Y9 = 49 Convention from Allers+07: M0 = 0, L0 = 10, ... Returns ------- spt : str String representing the spectral index """ if convention == 'splat': if idx < 10: spt = 'K' elif idx < 20: spt = 'M' elif idx < 30: spt = 'L' elif idx < 40: spt = 'T' elif idx < 50: spt = 'Y' else: raise ValueError("Index not between 0 (K0) and 49 (Y9)") elif convention == 'Allers07': if idx < 10: spt = 'M' elif idx < 20: spt = 'L' else: raise ValueError("Index not between 0 (M0) and 20 (L9)") if not (idx%10)%1: spt+="{:.0f}".format(idx%10) else: spt+="{:.1f}".format(idx%10) return spt