def resample_spectrum( self, wavel_points: np.ndarray, model_param: Optional[Dict[str, float]] = None, spec_res: Optional[float] = None, apply_mask: bool = False, ) -> box.SpectrumBox: """ Function for resampling the spectrum and optional uncertainties onto a new wavelength grid. Parameters ---------- wavel_points : np.ndarray Wavelengths (um). model_param : dict, None Dictionary with the model parameters, which should only contain the ``'scaling'`` keyword. No scaling is applied if the argument of ``model_param`` is set to ``None``. spec_res : float, None Spectral resolution that is used for smoothing the spectrum before resampling the wavelengths. No smoothing is applied if the argument is set to ``None``. The smoothing can only be applied ti spectra with a constant spectral resolution (which is the case for all model spectra that are compatible with ``species``) or a constant wavelength spacing. The first smoothing approach is fastest. apply_mask : bool Exclude negative values and NaNs. Returns ------- species.core.box.SpectrumBox Box with the resampled spectrum. """ calibbox = self.get_spectrum(apply_mask=apply_mask) if spec_res is not None: calibbox.flux = read_util.smooth_spectrum( wavelength=calibbox.wavelength, flux=calibbox.flux, spec_res=spec_res) flux_new, error_new = spectres.spectres( wavel_points, calibbox.wavelength, calibbox.flux, spec_errs=calibbox.error, fill=0.0, verbose=False, ) if model_param is not None: flux_new = model_param["scaling"] * flux_new error_new = model_param["scaling"] * error_new return box.create_box( boxtype="spectrum", spectrum="calibration", wavelength=wavel_points, flux=flux_new, error=error_new, name=self.tag, )
def get_spectrum(self, model_param: Dict[str, Union[float, List[float]]], spec_res: float, smooth: bool = False) -> box.ModelBox: """ Function for calculating a Planck spectrum or a combination of multiple Planck spectra. Parameters ---------- model_param : dict Dictionary with the 'teff' (K), 'radius' (Rjup), and 'distance' (pc). The values of 'teff' and 'radius' can be a single float, or a list with floats for a combination of multiple Planck functions, e.g. {'teff': [1500., 1000.], 'radius': [1., 2.], 'distance': 10.}. spec_res : float Spectral resolution. smooth : bool Returns ------- species.core.box.ModelBox Box with the Planck spectrum. """ if 'teff' in model_param and isinstance(model_param['teff'], list): model_param = self.update_parameters(model_param) wavel_points = read_util.create_wavelengths(self.wavel_range, spec_res) n_planck = 0 for item in model_param: if item[:4] == 'teff': n_planck += 1 if n_planck == 1: if 'radius' in model_param and 'distance' in model_param: scaling = ((model_param['radius']*constants.R_JUP) / (model_param['distance']*constants.PARSEC))**2 else: scaling = 1. flux = self.planck(wavel_points, model_param['teff'], scaling) # (W m-2 um-1) else: flux = np.zeros(wavel_points.shape) for i in range(n_planck): if f'radius_{i}' in model_param and 'distance' in model_param: scaling = ((model_param[f'radius_{i}']*constants.R_JUP) / (model_param['distance']*constants.PARSEC))**2 else: scaling = 1. flux += self.planck(wavel_points, model_param[f'teff_{i}'], scaling) # (W m-2 um-1) if smooth: flux = read_util.smooth_spectrum(wavel_points, flux, spec_res) model_box = box.create_box(boxtype='model', model='planck', wavelength=wavel_points, flux=flux, parameters=model_param, quantity='flux') if n_planck == 1 and 'radius' in model_param: model_box.parameters['luminosity'] = 4. * np.pi * ( model_box.parameters['radius'] * constants.R_JUP)**2 * constants.SIGMA_SB * \ model_box.parameters['teff']**4. / constants.L_SUN # (Lsun) elif n_planck > 1: lum_total = 0. for i in range(n_planck): if f'radius_{i}' in model_box.parameters: # Add up the luminosity of the blackbody components (Lsun) surface = 4. * np.pi * (model_box.parameters[f'radius_{i}']*constants.R_JUP)**2 lum_total += surface * constants.SIGMA_SB * \ model_box.parameters[f'teff_{i}']**4. / constants.L_SUN if lum_total > 0.: model_box.parameters['luminosity'] = lum_total return model_box
def spectral_type( self, tag: str, spec_library, wavel_range: Optional[Tuple[Optional[float], Optional[float]]] = None, sptypes: Optional[List[str]] = None, av_ext: Optional[Union[List[float], np.array]] = None, rad_vel: Optional[Union[List[float], np.array]] = None, ) -> None: """ Method for finding the best fitting empirical spectra from a selected library by evaluating the goodness-of-fit statistic from Cushing et al. (2008). Parameters ---------- tag : str Database tag where for each spectrum from the spectral library the best-fit parameters will be stored. So when testing a range of values for ``av_ext`` and ``rad_vel``, only the parameters that minimize the goodness-of-fit statistic will be stored. spec_library : str Name of the spectral library ('irtf', 'spex', 'kesseli+2017', 'bonnefoy+2014'). wavel_range : tuple(float, float), None Wavelength range (um) that is used for the empirical comparison. sptypes : list(str), None List with spectral types to compare with. The list should only contains types, for example ``sptypes=['M', 'L']``. All available spectral types in the ``spec_library`` are compared with if set to ``None``. av_ext : list(float), np.array, None List of A_V extinctions for which the goodness-of-fit statistic is tested. The extinction is calculated with the empirical relation from Cardelli et al. (1989). rad_vel : list(float), np.array, None List of radial velocities (km s-1) for which the goodness-of-fit statistic is tested. Returns ------- NoneType None """ w_i = 1.0 if av_ext is None: av_ext = [0.0] if rad_vel is None: rad_vel = [0.0] h5_file = h5py.File(self.database, "r") try: h5_file[f"spectra/{spec_library}"] except KeyError: h5_file.close() species_db = database.Database() species_db.add_spectra(spec_library) h5_file = h5py.File(self.database, "r") # Read object spectra and resolution obj_spec = [] obj_res = [] for item in self.spec_name: obj_spec.append(self.object.get_spectrum()[item][0]) obj_res.append(self.object.get_spectrum()[item][3]) # Read inverted covariance matrix # obj_inv_cov = self.object.get_spectrum()[self.spec_name][2] # Create empty lists for results name_list = [] spt_list = [] gk_list = [] ck_list = [] av_list = [] rv_list = [] print_message = "" # Start looping over library spectra for i, item in enumerate(h5_file[f"spectra/{spec_library}"]): # Read spectrum spectral type from library dset = h5_file[f"spectra/{spec_library}/{item}"] if isinstance(dset.attrs["sptype"], str): item_sptype = dset.attrs["sptype"] else: # Use decode for backward compatibility item_sptype = dset.attrs["sptype"].decode("utf-8") if item_sptype == "None": continue if sptypes is None or item_sptype[0] in sptypes: # Convert HDF5 dataset into numpy array spectrum = np.asarray(dset) if wavel_range is not None: # Select subset of the spectrum if wavel_range[0] is None: indices = np.where( (spectrum[:, 0] < wavel_range[1]))[0] elif wavel_range[1] is None: indices = np.where( (spectrum[:, 0] > wavel_range[0]))[0] else: indices = np.where((spectrum[:, 0] > wavel_range[0]) & (spectrum[:, 0] < wavel_range[1]))[0] if len(indices) == 0: raise ValueError( "The selected wavelength range does not cover any " "wavelength points of the input spectrum. Please " "use a broader range as argument of 'wavel_range'." ) spectrum = spectrum[indices, ] empty_message = len(print_message) * " " print(f"\r{empty_message}", end="") print_message = f"Processing spectra... {item}" print(f"\r{print_message}", end="") # Loop over all values of A_V and RV that will be tested for av_item in av_ext: for rv_item in rad_vel: for j, spec_item in enumerate(obj_spec): # Dust extinction ism_ext = dust_util.ism_extinction( av_item, 3.1, spectrum[:, 0]) flux_scaling = 10.0**(-0.4 * ism_ext) # Shift wavelengths by RV wavel_shifted = (spectrum[:, 0] + spectrum[:, 0] * 1e3 * rv_item / constants.LIGHT) # Smooth spectrum flux_smooth = read_util.smooth_spectrum( wavel_shifted, spectrum[:, 1] * flux_scaling, spec_res=obj_res[j], force_smooth=True, ) # Interpolate library spectrum to object wavelengths interp_spec = interp1d( spectrum[:, 0], flux_smooth, kind="linear", fill_value="extrapolate", ) indices = np.where( (spec_item[:, 0] > np.amin(spectrum[:, 0])) & (spec_item[:, 0] < np.amax(spectrum[:, 0])))[0] flux_resample = interp_spec(spec_item[indices, 0]) c_numer = (w_i * spec_item[indices, 1] * flux_resample / spec_item[indices, 2]**2) c_denom = (w_i * flux_resample**2 / spec_item[indices, 2]**2) if j == 0: g_k = 0.0 c_k_spec = [] c_k = np.sum(c_numer) / np.sum(c_denom) c_k_spec.append(c_k) chi_sq = (spec_item[indices, 1] - c_k * flux_resample) / spec_item[indices, 2] g_k += np.sum(w_i * chi_sq**2) # obj_inv_cov_crop = obj_inv_cov[indices, :] # obj_inv_cov_crop = obj_inv_cov_crop[:, indices] # g_k = np.dot(spec_item[indices, 1]-c_k*flux_resample, # np.dot(obj_inv_cov_crop, # spec_item[indices, 1]-c_k*flux_resample)) # Append to the lists of results name_list.append(item) spt_list.append(item_sptype) gk_list.append(g_k) ck_list.append(c_k_spec) av_list.append(av_item) rv_list.append(rv_item) empty_message = len(print_message) * " " print(f"\r{empty_message}", end="") print("\rProcessing spectra... [DONE]") h5_file.close() name_list = np.asarray(name_list) spt_list = np.asarray(spt_list) gk_list = np.asarray(gk_list) ck_list = np.asarray(ck_list) av_list = np.asarray(av_list) rv_list = np.asarray(rv_list) sort_index = np.argsort(gk_list) name_list = name_list[sort_index] spt_list = spt_list[sort_index] gk_list = gk_list[sort_index] ck_list = ck_list[sort_index] av_list = av_list[sort_index] rv_list = rv_list[sort_index] name_select = [] spt_select = [] gk_select = [] ck_select = [] av_select = [] rv_select = [] for i, item in enumerate(name_list): if item not in name_select: name_select.append(item) spt_select.append(spt_list[i]) gk_select.append(gk_list[i]) ck_select.append(ck_list[i]) av_select.append(av_list[i]) rv_select.append(rv_list[i]) print("Best-fitting spectra:") if len(gk_select) < 10: for i, gk_item in enumerate(gk_select): print( f" {i+1:2d}. G = {gk_item:.2e} -> {name_select[i]}, {spt_select[i]}, " f"A_V = {av_select[i]:.2f}, RV = {rv_select[i]:.0f} km/s,\n" f" scalings = {ck_select[i]}") else: for i in range(10): print( f" {i+1:2d}. G = {gk_select[i]:.2e} -> {name_select[i]}, {spt_select[i]}, " f"A_V = {av_select[i]:.2f}, RV = {rv_select[i]:.0f} km/s,\n" f" scalings = {ck_select[i]}") species_db = database.Database() species_db.add_empirical( tag=tag, names=name_select, sptypes=spt_select, goodness_of_fit=gk_select, flux_scaling=ck_select, av_ext=av_select, rad_vel=rv_select, object_name=self.object_name, spec_name=self.spec_name, spec_library=spec_library, )
def get_model(self, model_param: Dict[str, float], spec_res: Optional[float] = None, wavel_resample: Optional[np.ndarray] = None, magnitude: bool = False, smooth: bool = False) -> box.ModelBox: """ Function for extracting a model spectrum by linearly interpolating the model grid. Parameters ---------- model_param : dict Dictionary with the model parameters and values. The values should be within the boundaries of the grid. The grid boundaries of the spectra in the database can be obtained with :func:`~species.read.read_model.ReadModel.get_bounds()`. spec_res : float, None Spectral resolution that is used for smoothing the spectrum with a Gaussian kernel when ``smooth=True`` and/or resampling the spectrum when ``wavel_range`` of ``FitModel`` is not ``None``. The original wavelength points are used if both ``spec_res`` and ``wavel_resample`` are set to ``None``, or if ``smooth`` is set to ``True``. wavel_resample : np.ndarray, None Wavelength points (um) to which the spectrum is resampled. In that case, ``spec_res`` can still be used for smoothing the spectrum with a Gaussian kernel. magnitude : bool Normalize the spectrum with a flux calibrated spectrum of Vega and return the magnitude instead of flux density. smooth : bool If ``True``, the spectrum is smoothed with a Gaussian kernel to the spectral resolution of ``spec_res``. This requires either a uniform spectral resolution of the input spectra (fast) or a uniform wavelength spacing of the input spectra (slow). Returns ------- species.core.box.ModelBox Box with the model spectrum. """ if smooth and spec_res is None: warnings.warn('The \'spec_res\' argument is required for smoothing the spectrum when ' '\'smooth\' is set to True.') grid_bounds = self.get_bounds() extra_param = ['radius', 'distance', 'mass', 'luminosity', 'lognorm_radius', 'lognorm_sigma', 'lognorm_ext', 'ism_ext', 'ism_red', 'powerlaw_max', 'powerlaw_exp', 'powerlaw_ext'] for key in self.get_parameters(): if key not in model_param.keys(): raise ValueError(f'The \'{key}\' parameter is required by \'{self.model}\'. ' f'The mandatory parameters are {self.get_parameters()}.') if model_param[key] < grid_bounds[key][0]: raise ValueError(f'The input value of \'{key}\' is smaller than the lower ' f'boundary of the model grid ({model_param[key]} < ' f'{grid_bounds[key][0]}).') if model_param[key] > grid_bounds[key][1]: raise ValueError(f'The input value of \'{key}\' is larger than the upper ' f'boundary of the model grid ({model_param[key]} > ' f'{grid_bounds[key][1]}).') for key in model_param.keys(): if key not in self.get_parameters() and key not in extra_param: warnings.warn(f'The \'{key}\' parameter is not required by \'{self.model}\' so ' f'the parameter will be ignored. The mandatory parameters are ' f'{self.get_parameters()}.') if 'mass' in model_param and 'radius' not in model_param: mass = 1e3 * model_param['mass'] * constants.M_JUP # (g) radius = math.sqrt(1e3 * constants.GRAVITY * mass / (10.**model_param['logg'])) # (cm) model_param['radius'] = 1e-2 * radius / constants.R_JUP # (Rjup) if self.spectrum_interp is None: self.interpolate_model() if self.wavel_range is None: wl_points = self.get_wavelengths() self.wavel_range = (wl_points[0], wl_points[-1]) parameters = [] if 'teff' in model_param: parameters.append(model_param['teff']) if 'logg' in model_param: parameters.append(model_param['logg']) if 'feh' in model_param: parameters.append(model_param['feh']) if 'co' in model_param: parameters.append(model_param['co']) if 'fsed' in model_param: parameters.append(model_param['fsed']) flux = self.spectrum_interp(parameters)[0] if 'radius' in model_param: model_param['mass'] = read_util.get_mass(model_param['logg'], model_param['radius']) if 'distance' in model_param: scaling = (model_param['radius']*constants.R_JUP)**2 / \ (model_param['distance']*constants.PARSEC)**2 flux *= scaling if smooth: flux = read_util.smooth_spectrum(wavelength=self.wl_points, flux=flux, spec_res=spec_res) if wavel_resample is not None: flux = spectres.spectres(wavel_resample, self.wl_points, flux, spec_errs=None, fill=np.nan, verbose=True) elif spec_res is not None and not smooth: index = np.where(np.isnan(flux))[0] if index.size > 0: raise ValueError('Flux values should not contains NaNs. Please make sure that ' 'the parameter values and the wavelength range are within ' 'the grid boundaries as stored in the database.') wavel_resample = read_util.create_wavelengths( (self.wl_points[0], self.wl_points[-1]), spec_res) indices = np.where((wavel_resample > self.wl_points[0]) & (wavel_resample < self.wl_points[-2]))[0] wavel_resample = wavel_resample[indices] flux = spectres.spectres(wavel_resample, self.wl_points, flux, spec_errs=None, fill=np.nan, verbose=True) if magnitude: quantity = 'magnitude' with h5py.File(self.database, 'r') as h5_file: try: h5_file['spectra/calibration/vega'] except KeyError: h5_file.close() species_db = database.Database() species_db.add_spectrum('vega') h5_file = h5py.File(self.database, 'r') readcalib = read_calibration.ReadCalibration('vega', filter_name=None) calibbox = readcalib.get_spectrum() if wavel_resample is not None: new_spec_wavs = wavel_resample else: new_spec_wavs = self.wl_points flux_vega, _ = spectres.spectres(new_spec_wavs, calibbox.wavelength, calibbox.flux, spec_errs=calibbox.error, fill=np.nan, verbose=True) flux = -2.5*np.log10(flux/flux_vega) else: quantity = 'flux' if np.isnan(np.sum(flux)): warnings.warn(f'The resampled spectrum contains {np.sum(np.isnan(flux))} NaNs, ' f'probably because the original wavelength range does not fully ' f'encompass the new wavelength range. The happened with the ' f'following parameters: {model_param}.') if wavel_resample is None: wavelength = self.wl_points else: wavelength = wavel_resample # is_finite = np.where(np.isfinite(flux))[0] # # if wavel_resample is None: # wavelength = self.wl_points[is_finite] # else: # wavelength = wavel_resample[is_finite] # # if wavelength.shape[0] == 0: # raise ValueError(f'The model spectrum is empty. Perhaps the grid could not be ' # f'interpolated at {model_param} because zeros are stored in the ' # f'database.') model_box = box.create_box(boxtype='model', model=self.model, wavelength=wavelength, flux=flux, parameters=model_param, quantity=quantity) if 'lognorm_radius' in model_param and 'lognorm_sigma' in model_param and \ 'lognorm_ext' in model_param: model_box.flux = self.apply_lognorm_ext(model_box.wavelength, model_box.flux, model_param['lognorm_radius'], model_param['lognorm_sigma'], model_param['lognorm_ext']) if 'powerlaw_max' in model_param and 'powerlaw_exp' in model_param and \ 'powerlaw_ext' in model_param: model_box.flux = self.apply_powerlaw_ext(model_box.wavelength, model_box.flux, model_param['powerlaw_max'], model_param['powerlaw_exp'], model_param['powerlaw_ext']) if 'ism_ext' in model_param and 'ism_red' in model_param: model_box.flux = self.apply_ism_ext(model_box.wavelength, model_box.flux, model_param['ism_ext'], model_param['ism_red']) if 'radius' in model_box.parameters: model_box.parameters['luminosity'] = 4. * np.pi * ( model_box.parameters['radius'] * constants.R_JUP)**2 * constants.SIGMA_SB * \ model_box.parameters['teff']**4. / constants.L_SUN # (Lsun) return model_box
def get_spectrum( self, model_param: Dict[str, Union[float, List[float]]], spec_res: float, smooth: bool = False, wavel_resample: Optional[np.ndarray] = None, ) -> box.ModelBox: """ Function for calculating a Planck spectrum or a combination of multiple Planck spectra. The spectrum is calculated at :math:`R = 500`. Afterwards, an optional smoothing and wavelength resampling can be applied. Parameters ---------- model_param : dict Dictionary with the 'teff' (K), 'radius' (Rjup), and 'parallax' (mas) or 'distance' (pc). The values of 'teff' and 'radius' can be a single float, or a list with floats for a combination of multiple Planck functions, e.g. {'teff': [1500., 1000.], 'radius': [1., 2.], 'distance': 10.}. spec_res : float Spectral resolution that is used for smoothing the spectrum with a Gaussian kernel when ``smooth=True``. smooth : bool If ``True``, the spectrum is smoothed to the spectral resolution of ``spec_res``. wavel_resample : np.ndarray, None Wavelength points (um) to which the spectrum will be resampled. The resampling is applied after the optional smoothing to ``spec_res`` when ``smooth=True``. Returns ------- species.core.box.ModelBox Box with the Planck spectrum. """ if "teff" in model_param and isinstance(model_param["teff"], list): model_param = self.update_parameters(model_param) wavel_points = read_util.create_wavelengths(self.wavel_range, 500.0) n_planck = 0 for item in model_param: if item[:4] == "teff": n_planck += 1 if n_planck == 1: if "radius" in model_param and "parallax" in model_param: scaling = ( (model_param["radius"] * constants.R_JUP) / (1e3 * constants.PARSEC / model_param["parallax"]) ) ** 2 elif "radius" in model_param and "distance" in model_param: scaling = ( (model_param["radius"] * constants.R_JUP) / (model_param["distance"] * constants.PARSEC) ) ** 2 else: scaling = 1.0 flux = self.planck( wavel_points, model_param["teff"], scaling ) # (W m-2 um-1) else: flux = np.zeros(wavel_points.shape) for i in range(n_planck): if f"radius_{i}" in model_param and "parallax" in model_param: scaling = ( (model_param[f"radius_{i}"] * constants.R_JUP) / (1e3 * constants.PARSEC / model_param["parallax"]) ) ** 2 elif f"radius_{i}" in model_param and "distance" in model_param: scaling = ( (model_param[f"radius_{i}"] * constants.R_JUP) / (model_param["distance"] * constants.PARSEC) ) ** 2 else: scaling = 1.0 flux += self.planck( wavel_points, model_param[f"teff_{i}"], scaling ) # (W m-2 um-1) if smooth: flux = read_util.smooth_spectrum(wavel_points, flux, spec_res) model_box = box.create_box( boxtype="model", model="planck", wavelength=wavel_points, flux=flux, parameters=model_param, quantity="flux", ) if wavel_resample is not None: flux = spectres.spectres( wavel_resample, wavel_points, flux, spec_errs=None, fill=np.nan, verbose=True, ) model_box.wavelength = wavel_resample model_box.flux = flux if n_planck == 1 and "radius" in model_param: model_box.parameters["luminosity"] = ( 4.0 * np.pi * (model_box.parameters["radius"] * constants.R_JUP) ** 2 * constants.SIGMA_SB * model_box.parameters["teff"] ** 4.0 / constants.L_SUN ) # (Lsun) elif n_planck > 1: lum_total = 0.0 for i in range(n_planck): if f"radius_{i}" in model_box.parameters: # Add up the luminosity of the blackbody components (Lsun) surface = ( 4.0 * np.pi * (model_box.parameters[f"radius_{i}"] * constants.R_JUP) ** 2 ) lum_total += ( surface * constants.SIGMA_SB * model_box.parameters[f"teff_{i}"] ** 4.0 / constants.L_SUN ) if lum_total > 0.0: model_box.parameters["luminosity"] = lum_total return model_box
def plot_empirical_spectra( tag: str, n_spectra: int, flux_offset: Optional[float] = None, label_pos: Optional[Tuple[float, float]] = None, xlim: Optional[Tuple[float, float]] = None, ylim: Optional[Tuple[float, float]] = None, title: Optional[str] = None, offset: Optional[Tuple[float, float]] = None, figsize: Optional[Tuple[float, float]] = (4.0, 2.5), output: Optional[str] = "empirical.pdf", ): """ Function for plotting the results from the empirical spectrum comparison. Parameters ---------- tag : str Database tag where the results from the empirical comparison with :class:`~species.analysis.empirical.CompareSpectra.spectral_type` are stored. n_spectra : int The number of spectra with the lowest goodness-of-fit statistic that will be plotted in comparison with the data. label_pos : tuple(float, float), None Position for the name labels. Should be provided as (x, y) for the lowest spectrum. The ``flux_offset`` will be applied to the remaining spectra. The labels are only plotted if the argument of both ``label_pos`` and ``flux_offset`` are not ``None``. flux_offset : float, None Offset to be applied such that the spectra do not overlap. No offset is applied if the argument is set to ``None``. xlim : tuple(float, float) Limits of the spectral type axis. ylim : tuple(float, float) Limits of the goodness-of-fit axis. title : str Plot title. offset : tuple(float, float) Offset for the label of the x- and y-axis. figsize : tuple(float, float) Figure size. output : str Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- NoneType None """ if output is None: print("Plotting empirical spectra comparison...", end="") else: print(f"Plotting empirical spectra comparison: {output}...", end="") if flux_offset is None: flux_offset = 0.0 config_file = os.path.join(os.getcwd(), "species_config.ini") config = configparser.ConfigParser() config.read(config_file) db_path = config["species"]["database"] h5_file = h5py.File(db_path, "r") dset = h5_file[f"results/empirical/{tag}/names"] object_name = dset.attrs["object_name"] spec_library = dset.attrs["spec_library"] n_spec_name = dset.attrs["n_spec_name"] spec_name = [] for i in range(n_spec_name): spec_name.append(dset.attrs[f"spec_name{i}"]) names = np.array(dset) flux_scaling = np.array(h5_file[f"results/empirical/{tag}/flux_scaling"]) av_ext = np.array(h5_file[f"results/empirical/{tag}/av_ext"]) rad_vel = np.array(h5_file[f"results/empirical/{tag}/rad_vel"]) rad_vel *= 1e3 # (m s-1) mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"] mpl.rcParams["font.family"] = "serif" plt.rc("axes", edgecolor="black", linewidth=2.2) plt.rcParams["axes.axisbelow"] = False plt.figure(1, figsize=figsize) gridsp = mpl.gridspec.GridSpec(1, 1) gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1) ax = plt.subplot(gridsp[0, 0]) ax.tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, ) ax.tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, ) ax.xaxis.set_minor_locator(AutoMinorLocator(5)) ax.yaxis.set_minor_locator(AutoMinorLocator(5)) ax.set_xlabel("Wavelength (µm)", fontsize=13) if flux_offset == 0.0: ax.set_ylabel(r"$\mathregular{F}_\lambda$ (W m$^{-2}$ µm$^{-1}$)", fontsize=11) else: ax.set_ylabel( r"$\mathregular{F}_\lambda$ (W m$^{-2}$ µm$^{-1}$) + offset", fontsize=11 ) if xlim is not None: ax.set_xlim(xlim[0], xlim[1]) if ylim is not None: ax.set_ylim(ylim[0], ylim[1]) if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.1) ax.get_yaxis().set_label_coords(-0.1, 0.5) if title is not None: ax.set_title(title, y=1.02, fontsize=13) read_obj = read_object.ReadObject(object_name) obj_spec = [] obj_res = [] for item in spec_name: obj_spec.append(read_obj.get_spectrum()[item][0]) obj_res.append(read_obj.get_spectrum()[item][3]) if flux_offset == 0.0: for spec_item in obj_spec: ax.plot(spec_item[:, 0], spec_item[:, 1], "-", lw=0.5, color="black") for i in range(n_spectra): if isinstance(names[i], str): name_item = names[i] else: name_item = names[i].decode("utf-8") dset = h5_file[f"spectra/{spec_library}/{name_item}"] sptype = dset.attrs["sptype"] spectrum = np.asarray(dset) if flux_offset != 0.0: for spec_item in obj_spec: ax.plot( spec_item[:, 0], (n_spectra - i - 1) * flux_offset + spec_item[:, 1], "-", lw=0.5, color="black", ) for j, spec_item in enumerate(obj_spec): ism_ext = dust_util.ism_extinction(av_ext[i], 3.1, spectrum[:, 0]) ext_scaling = 10.0 ** (-0.4 * ism_ext) wavel_shifted = ( spectrum[:, 0] + spectrum[:, 0] * rad_vel[i] / constants.LIGHT ) flux_smooth = read_util.smooth_spectrum( wavel_shifted, spectrum[:, 1] * ext_scaling, spec_res=obj_res[j], force_smooth=True, ) interp_spec = interp1d( spectrum[:, 0], flux_smooth, fill_value="extrapolate" ) indices = np.where( (obj_spec[j][:, 0] > np.amin(spectrum[:, 0])) & (obj_spec[j][:, 0] < np.amax(spectrum[:, 0])) )[0] flux_resample = interp_spec(obj_spec[j][indices, 0]) ax.plot( obj_spec[j][indices, 0], (n_spectra - i - 1) * flux_offset + flux_scaling[i][j] * flux_resample, color="tomato", lw=0.5, ) if label_pos is not None and flux_offset != 0.0: label_text = name_item + ", " + sptype if av_ext[i] != 0.0: label_text += r", A$_\mathregular{V}$ = " + f"{av_ext[i]:.1f}" ax.text( label_pos[0], label_pos[1] + (n_spectra - i - 1) * flux_offset, label_text, fontsize=8.0, ha="left", ) print(" [DONE]") if output is None: plt.show() else: plt.savefig(output, bbox_inches="tight") plt.clf() plt.close() h5_file.close()
def get_model(self, model_par, specres=None): """ Parameters ---------- model_par : dict Model parameter values. specres : float Spectral resolution, achieved by smoothing with a Gaussian kernel. The original wavelength points are used if set to None. Using a high spectral resolution is computationally faster if the original wavelength grid has a fine sampling. Returns ------- species.core.box.ModelBox Box with the model spectrum. """ if 'mass' in model_par: mass = 1e3 * model_par['mass'] * constants.M_JUP # [g] radius = math.sqrt(1e3 * constants.GRAVITY * mass / (10.**model_par['logg'])) # [cm] model_par['radius'] = 1e-2 * radius / constants.R_JUP # [Rjup] if self.spectrum_interp is None: self.interpolate() if self.wavelength is None: wl_points = self.get_wavelength() self.wavelength = (wl_points[0], wl_points[-1]) if self.model in ('drift-phoenix', 'bt-nextgen', 'petitcode_warm_clear'): parameters = [ model_par['teff'], model_par['logg'], model_par['feh'] ] elif self.model in ('bt-settl', 'ames-dusty', 'ames-cond'): parameters = [model_par['teff'], model_par['logg']] elif self.model == 'petitcode_warm_cloudy': parameters = [ model_par['teff'], model_par['logg'], model_par['feh'], model_par['fsed'] ] elif self.model == 'petitcode_hot_clear': parameters = [ model_par['teff'], model_par['logg'], model_par['feh'], model_par['co'] ] elif self.model == 'petitcode_hot_cloudy': parameters = [ model_par['teff'], model_par['logg'], model_par['feh'], model_par['co'], model_par['fsed'] ] flux = self.spectrum_interp(parameters)[0] if 'radius' in model_par: model_par['mass'] = read_util.get_mass(model_par) if 'distance' in model_par: scaling = (model_par['radius']*constants.R_JUP)**2 / \ (model_par['distance']*constants.PARSEC)**2 flux *= scaling if specres is not None: index = np.where(np.isnan(flux))[0] if index.size > 0: raise ValueError('Flux values should not contains NaNs.') flux = read_util.smooth_spectrum(wavelength=self.wl_points, flux=flux, specres=specres, size=11) return box.create_box(boxtype='model', model=self.model, wavelength=self.wl_points, flux=flux, parameters=model_par)