def gaussian_spectrum( wavel_range: Union[Tuple[float, float], Tuple[np.float32, np.float32]], model_param: Dict[str, float], spec_res: float = 100.0, double_gaussian: bool = False, ) -> box.ModelBox: """ Function for calculating a Gaussian spectrum (i.e. for an emission line). Parameters ---------- wavel_range : tuple(float, float) Tuple with the minimum and maximum wavelength (um). model_param : dict Dictionary with the model parameters. Should contain ``'gauss_amplitude'``, ``'gauss_mean'``, ``'gauss_sigma'``, and optionally ``'gauss_offset'``. spec_res : float Spectral resolution (default: 100). double_gaussian : bool Set to ``True`` for returning a double Gaussian function. In that case, ``model_param`` should also contain ``'gauss_amplitude_2'``, ``'gauss_mean_2'``, and ``'gauss_sigma_2'``. Returns ------- species.core.box.ModelBox Box with the Gaussian spectrum. """ wavel = create_wavelengths((wavel_range[0], wavel_range[1]), spec_res) gauss_exp = np.exp(-0.5 * (wavel - model_param["gauss_mean"])**2 / model_param["gauss_sigma"]**2) flux = model_param["gauss_amplitude"] * gauss_exp if double_gaussian: gauss_exp = np.exp(-0.5 * (wavel - model_param["gauss_mean_2"])**2 / model_param["gauss_sigma_2"]**2) flux += model_param["gauss_amplitude_2"] * gauss_exp if "gauss_offset" in model_param: flux += model_param["gauss_offset"] model_box = box.create_box( boxtype="model", model="gaussian", wavelength=wavel, flux=flux, parameters=model_param, quantity="flux", ) return model_box
def multi_photometry(datatype: str, spectrum: str, filters: List[str], parameters: Dict[str, float]) -> box.SynphotBox: """ Parameters ---------- datatype : str Data type ('model' or 'calibration'). spectrum : str Spectrum name (e.g., 'drift-phoenix', 'planck', 'powerlaw'). filters : list(str, ) List with the filter names. parameters : dict Dictionary with the model parameters. Returns ------- species.core.box.SynphotBox Box with synthetic photometry. """ print('Calculating synthetic photometry...', end='', flush=True) flux = {} if datatype == 'model': for item in filters: if spectrum == 'planck': readmodel = read_planck.ReadPlanck(filter_name=item) elif spectrum == 'powerlaw': synphot = photometry.SyntheticPhotometry(item) synphot.zero_point() # Set the wavel_range attribute powerl_box = read_util.powerlaw_spectrum(synphot.wavel_range, parameters) flux[item] = synphot.spectrum_to_flux(powerl_box.wavelength, powerl_box.flux)[0] else: readmodel = read_model.ReadModel(spectrum, filter_name=item) try: flux[item] = readmodel.get_flux(parameters)[0] except IndexError: flux[item] = np.nan warnings.warn(f'The wavelength range of the {item} filter does not match with ' f'the wavelength range of {spectrum}. The flux is set to NaN.') elif datatype == 'calibration': for item in filters: readcalib = read_calibration.ReadCalibration(spectrum, filter_name=item) flux[item] = readcalib.get_flux(parameters)[0] print(' [DONE]') return box.create_box('synphot', name='synphot', flux=flux)
def get_magnitude(self, sptypes: List[str] = None) -> box.PhotometryBox: """ Function for calculating the apparent magnitude for the ``filter_name``. Parameters ---------- sptypes : list(str) Spectral types to select from a library. The spectral types should be indicated with two characters (e.g. 'M5', 'L2', 'T3'). All spectra are selected if set to ``None``. Returns ------- species.core.box.PhotometryBox Box with the synthetic photometry. """ specbox = self.get_spectrum(sptypes=sptypes, exclude_nan=True) n_spectra = len(specbox.wavelength) filter_profile = read_filter.ReadFilter(filter_name=self.filter_name) mean_wavel = filter_profile.mean_wavelength() wavelengths = np.full(n_spectra, mean_wavel) filters = np.full(n_spectra, self.filter_name) synphot = photometry.SyntheticPhotometry(filter_name=self.filter_name) app_mag = [] abs_mag = [] for i in range(n_spectra): if np.isnan(specbox.distance[i][0]): app_tmp = (np.nan, np.nan) abs_tmp = (np.nan, np.nan) else: app_tmp, abs_tmp = synphot.spectrum_to_magnitude( specbox.wavelength[i], specbox.flux[i], error=specbox.error[i], distance=(float(specbox.distance[i][0]), float(specbox.distance[i][1]))) app_mag.append(app_tmp) abs_mag.append(abs_tmp) return box.create_box(boxtype='photometry', name=specbox.name, sptype=specbox.sptype, wavelength=wavelengths, flux=None, app_mag=np.asarray(app_mag), abs_mag=np.asarray(abs_mag), filter_name=filters)
def get_color_magnitude( temperatures: np.ndarray, radius: float, filters_color: Tuple[str, str], filter_mag: str, ) -> box.ColorMagBox: """ Function for calculating the colors and magnitudes in the range of 100-10000 K. Parameters ---------- temperatures : np.ndarray Temperatures (K) for which the colors and magnitude are calculated. radius : float Radius (Rjup). filters_color : tuple(str, str) Filter names for the color. filter_mag : str Filter name for the absolute magnitudes. Returns ------- species.core.box.ColorMagBox Box with the colors and magnitudes. """ list_color = [] list_mag = [] for item in temperatures: model_param = {"teff": item, "radius": radius, "distance": 10.0} read_planck_0 = ReadPlanck(filter_name=filters_color[0]) read_planck_1 = ReadPlanck(filter_name=filters_color[1]) read_planck_2 = ReadPlanck(filter_name=filter_mag) app_mag_0, _ = read_planck_0.get_magnitude(model_param) app_mag_1, _ = read_planck_1.get_magnitude(model_param) app_mag_2, _ = read_planck_2.get_magnitude(model_param) list_color.append(app_mag_0[0] - app_mag_1[0]) list_mag.append(app_mag_2[0]) return box.create_box( boxtype="colormag", library="planck", object_type=None, filters_color=filters_color, filter_mag=filter_mag, color=list_color, magnitude=list_mag, sptype=temperatures, names=None, )
def get_flux(self, sptypes: List[str] = None) -> box.PhotometryBox: """ Function for calculating the average flux density for the ``filter_name``. Parameters ---------- sptypes : list(str), None Spectral types to select from a library. The spectral types should be indicated with two characters (e.g. 'M5', 'L2', 'T3'). All spectra are selected if set to ``None``. Returns ------- species.core.box.PhotometryBox Box with the synthetic photometry. """ specbox = self.get_spectrum(sptypes=sptypes, exclude_nan=True) n_spectra = len(specbox.wavelength) filter_profile = read_filter.ReadFilter(filter_name=self.filter_name) mean_wavel = filter_profile.mean_wavelength() wavelengths = np.full(n_spectra, mean_wavel) filters = np.full(n_spectra, self.filter_name) synphot = photometry.SyntheticPhotometry(filter_name=self.filter_name) phot_flux = [] for i in range(n_spectra): flux = synphot.spectrum_to_flux( wavelength=specbox.wavelength[i], flux=specbox.flux[i], error=specbox.error[i], ) phot_flux.append(flux) phot_flux = np.asarray(phot_flux) return box.create_box( boxtype="photometry", name=specbox.name, sptype=specbox.sptype, wavelength=wavelengths, flux=phot_flux, app_mag=None, abs_mag=None, filter_name=filters, )
def get_color_color( temperatures: np.ndarray, radius: float, filters_colors: Tuple[Tuple[str, str], Tuple[str, str]], ) -> box.ColorColorBox: """ Function for calculating two colors in the range of 100-10000 K. Parameters ---------- temperatures : np.ndarray Temperatures (K) for which the colors are calculated. radius : float Radius (Rjup). filters_colors : tuple(tuple(str, str), tuple(str, str)) Two tuples with the filter names for the colors. Returns ------- species.core.box.ColorColorBox Box with the colors. """ list_color_1 = [] list_color_2 = [] for item in temperatures: model_param = {"teff": item, "radius": radius, "distance": 10.0} read_planck_0 = ReadPlanck(filter_name=filters_colors[0][0]) read_planck_1 = ReadPlanck(filter_name=filters_colors[0][1]) read_planck_2 = ReadPlanck(filter_name=filters_colors[1][0]) read_planck_3 = ReadPlanck(filter_name=filters_colors[1][1]) app_mag_0, _ = read_planck_0.get_magnitude(model_param) app_mag_1, _ = read_planck_1.get_magnitude(model_param) app_mag_2, _ = read_planck_2.get_magnitude(model_param) app_mag_3, _ = read_planck_3.get_magnitude(model_param) list_color_1.append(app_mag_0[0] - app_mag_1[0]) list_color_2.append(app_mag_2[0] - app_mag_3[0]) return box.create_box( boxtype="colorcolor", library="planck", object_type=None, filters=filters_colors, color1=list_color_1, color2=list_color_2, sptype=temperatures, names=None, )
def resample_spectrum(self, wavel_points: np.ndarray, model_param: Optional[Dict[str, float]] = None, apply_mask: bool = False) -> box.SpectrumBox: """ Function for resampling of a spectrum and uncertainties onto a new wavelength grid. Parameters ---------- wavel_points : np.ndarray Wavelength points (um). model_param : dict, None Model parameters. Should contain the 'scaling' value. Not used if set to ``None``. apply_mask : bool Exclude negative values and NaN values. Returns ------- species.core.box.SpectrumBox Box with the resampled spectrum. """ calibbox = self.get_spectrum() if apply_mask: indices = np.where(calibbox.flux > 0.)[0] calibbox.wavelength = calibbox.wavelength[indices] calibbox.flux = calibbox.flux[indices] calibbox.error = calibbox.error[indices] flux_new, error_new = spectres.spectres(wavel_points, calibbox.wavelength, calibbox.flux, spec_errs=calibbox.error, fill=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, simbad=None, sptype=None, distance=None)
def multi_photometry(datatype, spectrum, filters, parameters): """ Parameters ---------- datatype : str Data type ('model' or 'calibration'). spectrum : str Spectrum name (e.g., 'drift-phoenix'). filters : tuple(str, ) Filter names. parameters : dict Parameters and values for the spectrum Returns ------- species.core.box.SynphotBox Box with synthetic photometry. """ print('Calculating synthetic photometry...', end='', flush=True) flux = {} if datatype == 'model': for item in filters: if spectrum == 'planck': readmodel = read_planck.ReadPlanck(filter_name=item) else: readmodel = read_model.ReadModel(spectrum, filter_name=item) try: flux[item] = readmodel.get_flux(parameters)[0] except IndexError: flux[item] = np.nan warnings.warn(f'The wavelength range of the {item} filter does not match with ' f'the wavelength coverage of {spectrum}. The flux is set to NaN.') elif datatype == 'calibration': for item in filters: readcalib = read_calibration.ReadCalibration(spectrum, filter_name=item) flux[item] = readcalib.get_flux(parameters)[0] print(' [DONE]') return box.create_box('synphot', name='synphot', flux=flux)
def get_samples(self, tag, burnin=None, random=None): """ Parameters ---------- tag: str Database tag with the samples. burnin : int Number of burnin samples to exclude. All samples are selected if set to None. random : int Number of random samples to select. All samples (with the burnin excluded) are selected if set to None. Returns ------- species.core.box.SamplesBox Box with the MCMC samples. """ h5_file = h5py.File(self.database, 'r') dset = h5_file['results/mcmc/' + tag] spectrum = dset.attrs['spectrum'] nparam = dset.attrs['nparam'] samples = np.asarray(dset) if burnin: samples = samples[:, burnin:, :] if random: ran_walker = np.random.randint(samples.shape[0], size=random) ran_step = np.random.randint(samples.shape[1], size=random) samples = samples[ran_walker, ran_step, :] param = [] chisquare = [] for i in range(nparam): param.append(dset.attrs['parameter' + str(i)]) chisquare.append(dset.attrs['chisquare' + str(i)]) h5_file.close() return box.create_box('samples', spectrum=spectrum, parameters=param, samples=samples, chisquare=chisquare)
def powerlaw_spectrum( wavel_range: Union[Tuple[float, float], Tuple[np.float32, np.float32]], model_param: Dict[str, float], spec_res: float = 100.0, ) -> box.ModelBox: """ Function for calculating a power-law spectrum. The power-law function is calculated in log(wavelength)-log(flux) space but stored in the :class:`~species.core.box.ModelBox` in linear wavelength-flux space. Parameters ---------- wavel_range : tuple(float, float) Tuple with the minimum and maximum wavelength (um). model_param : dict Dictionary with the model parameters. Should contain `'log_powerlaw_a'`, `'log_powerlaw_b'`, and `'log_powerlaw_c'`. spec_res : float Spectral resolution (default: 100). Returns ------- species.core.box.ModelBox Box with the power-law spectrum. """ wavel = create_wavelengths((wavel_range[0], wavel_range[1]), spec_res) log_flux = (model_param["log_powerlaw_a"] + model_param["log_powerlaw_b"] * np.log10(wavel)**model_param["log_powerlaw_c"]) model_box = box.create_box( boxtype="model", model="powerlaw", wavelength=wavel, flux=10.0**log_flux, parameters=model_param, quantity="flux", ) return model_box
def get_planck(temperature, radius, distance, wavelength, specres): """ Parameters ---------- temperature : float Temperature (K). radius : float Radius (Rjup). distance : float Distance (pc). wavelength : tuple(float, float) Wavelength range (micron). specres : float Spectral resolution Returns ------- species.core.box.SpectrumBox Box with the Planck spectrum. """ wl_points = [wavelength[0]] while wl_points[-1] <= wavelength[1]: wl_points.append(wl_points[-1] + wl_points[-1] / specres) wl_points = np.asarray(wl_points) # [micron] scaling = (radius * con.R_JUP / (distance * con.PARSEC))**2 flux = planck(np.copy(wl_points), temperature, scaling) # [W m-2 micron-1] return box.create_box(boxtype='spectrum', spectrum='planck', wavelength=wl_points, flux=flux, error=None, name=None, simbad=None, sptype=None, distance=None)
def multi_photometry(datatype, spectrum, filters, parameters): """ Parameters ---------- datatype : str Data type ('model' or 'calibration'). spectrum : str Spectrum name (e.g., 'drift-phoenix'). filters : tuple(str, ) Filter IDs. parameters : dict Parameters and values for the spectrum Returns ------- species.core.box.SynphotBox Box with synthetic photometry. """ sys.stdout.write('Calculating synthetic photometry...') sys.stdout.flush() flux = {} if datatype == 'model': for item in filters: readmodel = read_model.ReadModel(spectrum, item) flux[item] = readmodel.get_photometry(parameters) elif datatype == 'calibration': for item in filters: readcalib = read_calibration.ReadCalibration(spectrum, item) flux[item] = readcalib.get_photometry(parameters) sys.stdout.write(' [DONE]\n') sys.stdout.flush() return box.create_box('synphot', name='synphot', flux=flux)
def get_isochrone(self, age, mass, filters_color, filter_mag): """ Parameters ---------- age : str Age (Myr) that is used to interpolate the isochrone data. mass : numpy.ndarray Masses (Mjup) for which the isochrone data is interpolated. filters_color : tuple(str, str), None Filter IDs for the color as listed in the file with the isochrone data. Not selected if set to None or if only evolutionary tracks are available. filter_mag : str, None Filter ID for the absolute magnitude as listed in the file with the isochrone data. Not selected if set to None or if only evolutionary tracks are available. Returns ------- species.core.box.IsochroneBox Box with the isochrone data. """ age_points = np.repeat(age, mass.shape[0]) # [Myr] color = None mag_abs = None index_teff = 2 index_logg = 4 with h5py.File(self.database, 'r') as h5_file: model = h5_file['isochrones/' + self.tag + '/evolution'].attrs['model'] evolution = np.asarray(h5_file['isochrones/' + self.tag + '/evolution']) if model == 'baraffe': filters = list(h5_file['isochrones/' + self.tag + '/filters']) magnitudes = np.asarray(h5_file['isochrones/' + self.tag + '/magnitudes']) if model == 'baraffe': for i, item in enumerate(filters): filters[i] = item.decode() if filters_color is not None: index_color_1 = filters.index(filters_color[0]) index_color_2 = filters.index(filters_color[1]) if filter_mag is not None: index_mag = filters.index(filter_mag) if filters_color is not None: mag_color_1 = griddata(points=evolution[:, 0:2], values=magnitudes[:, index_color_1], xi=np.stack((age_points, mass), axis=1), method='linear', fill_value='nan', rescale=False) mag_color_2 = griddata(points=evolution[:, 0:2], values=magnitudes[:, index_color_2], xi=np.stack((age_points, mass), axis=1), method='linear', fill_value='nan', rescale=False) color = mag_color_1 - mag_color_2 if filter_mag is not None: mag_abs = griddata(points=evolution[:, 0:2], values=magnitudes[:, index_mag], xi=np.stack((age_points, mass), axis=1), method='linear', fill_value='nan', rescale=False) teff = griddata(points=evolution[:, 0:2], values=evolution[:, index_teff], xi=np.stack((age_points, mass), axis=1), method='linear', fill_value='nan', rescale=False) logg = griddata(points=evolution[:, 0:2], values=evolution[:, index_logg], xi=np.stack((age_points, mass), axis=1), method='linear', fill_value='nan', rescale=False) return box.create_box(boxtype='isochrone', model=self.tag, filters_color=filters_color, filter_mag=filter_mag, color=color, magnitude=mag_abs, teff=teff, logg=logg, mass=mass)
def get_color_magnitude(self, age, mass, model, filters_color, filter_mag): """ Parameters ---------- age : str Age (Myr) that is used to interpolate the isochrone data. mass : numpy.ndarray Masses (Mjup) for which the isochrone data is interpolated. model : str Atmospheric model used to compute the synthetic photometry. filters_color : tuple(str, str), None Filter IDs for the color as listed in the file with the isochrone data. Not selected if set to None or if only evolutionary tracks are available. filter_mag : str, None Filter ID for the absolute magnitude as listed in the file with the isochrone data. Not selected if set to None or if only evolutionary tracks are available. Returns ------- species.core.box.ColorMagBox Box with the isochrone data. """ isochrone = self.get_isochrone(age=age, mass=mass, filters_color=None, filter_mag=None) model1 = read_model.ReadModel(model=model, wavelength=filters_color[0]) model2 = read_model.ReadModel(model=model, wavelength=filters_color[1]) mag1 = np.zeros(isochrone.mass.shape[0]) mag2 = np.zeros(isochrone.mass.shape[0]) for i, item in enumerate(isochrone.mass): model_par = { 'teff': isochrone.teff[i], 'logg': isochrone.logg[i], 'feh': 0., 'mass': item, 'distance': 10. } mag1[i], _ = model1.get_magnitude(model_par=model_par) mag2[i], _ = model2.get_magnitude(model_par=model_par) if filter_mag == filters_color[0]: abs_mag = mag1 elif filter_mag == filters_color[1]: abs_mag = mag2 else: raise ValueError( 'The filter_mag argument should be equal to one of the two filter ' 'values of filters_color.') return box.create_box(boxtype='colormag', library=model, object_type='temperature', filters_color=filters_color, filter_mag=filter_mag, color=mag1 - mag2, magnitude=abs_mag, sptype=isochrone.teff)
def multi_photometry( datatype: str, spectrum: str, filters: List[str], parameters: Dict[str, float], radtrans: Optional[read_radtrans.ReadRadtrans] = None, ) -> box.SynphotBox: """ Parameters ---------- datatype : str Data type ('model' or 'calibration'). spectrum : str Spectrum name (e.g., 'drift-phoenix', 'planck', 'powerlaw', 'petitradtrans'). filters : list(str) List with the filter names. parameters : dict Dictionary with the model parameters. radtrans : read_radtrans.ReadRadtrans, None Instance of :class:`~species.read.read_radtrans.ReadRadtrans`. Only required with ``spectrum='petitradtrans'`. Make sure that the ``wavel_range`` of the ``ReadRadtrans`` instance is sufficiently broad to cover all the ``filters``. Not used if set to `None`. Returns ------- species.core.box.SynphotBox Box with synthetic photometry. """ print("Calculating synthetic photometry...", end="", flush=True) flux = {} if datatype == "model": if spectrum == "petitradtrans": # Calculate the petitRADTRANS spectrum only once radtrans_box = radtrans.get_model(parameters) for item in filters: if spectrum == "petitradtrans": # Use an instance of SyntheticPhotometry instead # of get_flux from ReadRadtrans in order to not # recalculate the spectrum syn_phot = photometry.SyntheticPhotometry(item) flux[item], _ = syn_phot.spectrum_to_flux( radtrans_box.wavelength, radtrans_box.flux) elif spectrum == "powerlaw": synphot = photometry.SyntheticPhotometry(item) # Set the wavel_range attribute synphot.zero_point() powerl_box = read_util.powerlaw_spectrum( synphot.wavel_range, parameters) flux[item] = synphot.spectrum_to_flux(powerl_box.wavelength, powerl_box.flux)[0] else: if spectrum == "planck": readmodel = read_planck.ReadPlanck(filter_name=item) else: readmodel = read_model.ReadModel(spectrum, filter_name=item) try: if "teff_0" in parameters and "teff_1" in parameters: # Binary system param_0 = read_util.binary_to_single(parameters, 0) model_flux_0 = readmodel.get_flux(param_0)[0] param_1 = read_util.binary_to_single(parameters, 1) model_flux_1 = readmodel.get_flux(param_1)[0] flux[item] = ( parameters["spec_weight"] * model_flux_0 + (1.0 - parameters["spec_weight"]) * model_flux_1) else: # Single object flux[item] = readmodel.get_flux(parameters)[0] except IndexError: flux[item] = np.nan warnings.warn( f"The wavelength range of the {item} filter does not " f"match with the wavelength range of {spectrum}. The " f"flux is set to NaN.") elif datatype == "calibration": for item in filters: readcalib = read_calibration.ReadCalibration(spectrum, filter_name=item) flux[item] = readcalib.get_flux(parameters)[0] print(" [DONE]") return box.create_box("synphot", name="synphot", flux=flux)
def get_residuals( datatype: str, spectrum: str, parameters: Dict[str, float], objectbox: box.ObjectBox, inc_phot: Union[bool, List[str]] = True, inc_spec: Union[bool, List[str]] = True, radtrans: Optional[read_radtrans.ReadRadtrans] = None, ) -> box.ResidualsBox: """ Function for calculating the residuals from fitting model or calibration spectra to a set of spectra and/or photometry. Parameters ---------- datatype : str Data type ('model' or 'calibration'). spectrum : str Name of the atmospheric model or calibration spectrum. parameters : dict Parameters and values for the spectrum objectbox : species.core.box.ObjectBox Box with the photometry and/or spectra of an object. A scaling and/or error inflation of the spectra should be applied with :func:`~species.util.read_util.update_spectra` beforehand. inc_phot : bool, list(str) Include photometric data in the fit. If a boolean, either all (``True``) or none (``False``) of the data are selected. If a list, a subset of filter names (as stored in the database) can be provided. inc_spec : bool, list(str) Include spectroscopic data in the fit. If a boolean, either all (``True``) or none (``False``) of the data are selected. If a list, a subset of spectrum names (as stored in the database with :func:`~species.data.database.Database.add_object`) can be provided. radtrans : read_radtrans.ReadRadtrans, None Instance of :class:`~species.read.read_radtrans.ReadRadtrans`. Only required with ``spectrum='petitradtrans'`. Make sure that the ``wavel_range`` of the ``ReadRadtrans`` instance is sufficiently broad to cover all the photometric and spectroscopic data of ``inc_phot`` and ``inc_spec``. Not used if set to ``None``. Returns ------- species.core.box.ResidualsBox Box with the residuals. """ if isinstance(inc_phot, bool) and inc_phot: inc_phot = objectbox.filters if inc_phot: model_phot = multi_photometry( datatype=datatype, spectrum=spectrum, filters=inc_phot, parameters=parameters, radtrans=radtrans, ) res_phot = {} for item in inc_phot: transmission = read_filter.ReadFilter(item) res_phot[item] = np.zeros(objectbox.flux[item].shape) if objectbox.flux[item].ndim == 1: res_phot[item][0] = transmission.mean_wavelength() res_phot[item][1] = ( objectbox.flux[item][0] - model_phot.flux[item]) / objectbox.flux[item][1] elif objectbox.flux[item].ndim == 2: for j in range(objectbox.flux[item].shape[1]): res_phot[item][0, j] = transmission.mean_wavelength() res_phot[item][1, j] = ( objectbox.flux[item][0, j] - model_phot.flux[item]) / objectbox.flux[item][1, j] else: res_phot = None if inc_spec: res_spec = {} if spectrum == "petitradtrans": # Calculate the petitRADTRANS spectrum only once model = radtrans.get_model(parameters) for key in objectbox.spectrum: if isinstance(inc_spec, bool) or key in inc_spec: wavel_range = ( 0.9 * objectbox.spectrum[key][0][0, 0], 1.1 * objectbox.spectrum[key][0][-1, 0], ) wl_new = objectbox.spectrum[key][0][:, 0] spec_res = objectbox.spectrum[key][3] if spectrum == "planck": readmodel = read_planck.ReadPlanck(wavel_range=wavel_range) model = readmodel.get_spectrum(model_param=parameters, spec_res=1000.0) # Separate resampling to the new wavelength points flux_new = spectres.spectres( wl_new, model.wavelength, model.flux, spec_errs=None, fill=0.0, verbose=True, ) elif spectrum == "petitradtrans": # Separate resampling to the new wavelength points flux_new = spectres.spectres( wl_new, model.wavelength, model.flux, spec_errs=None, fill=0.0, verbose=True, ) else: # Resampling to the new wavelength points # is done by the get_model method readmodel = read_model.ReadModel(spectrum, wavel_range=wavel_range) if "teff_0" in parameters and "teff_1" in parameters: # Binary system param_0 = read_util.binary_to_single(parameters, 0) model_spec_0 = readmodel.get_model( param_0, spec_res=spec_res, wavel_resample=wl_new, smooth=True, ) param_1 = read_util.binary_to_single(parameters, 1) model_spec_1 = readmodel.get_model( param_1, spec_res=spec_res, wavel_resample=wl_new, smooth=True, ) flux_comb = ( parameters["spec_weight"] * model_spec_0.flux + (1.0 - parameters["spec_weight"]) * model_spec_1.flux) model_spec = box.create_box( boxtype="model", model=spectrum, wavelength=wl_new, flux=flux_comb, parameters=parameters, quantity="flux", ) else: # Single object model_spec = readmodel.get_model( parameters, spec_res=spec_res, wavel_resample=wl_new, smooth=True, ) flux_new = model_spec.flux data_spec = objectbox.spectrum[key][0] res_tmp = (data_spec[:, 1] - flux_new) / data_spec[:, 2] res_spec[key] = np.column_stack([wl_new, res_tmp]) else: res_spec = None print("Calculating residuals... [DONE]") print("Residuals (sigma):") if res_phot is not None: for item in inc_phot: if res_phot[item].ndim == 1: print(f" - {item}: {res_phot[item][1]:.2f}") elif res_phot[item].ndim == 2: for j in range(res_phot[item].shape[1]): print(f" - {item}: {res_phot[item][1, j]:.2f}") if res_spec is not None: for key in objectbox.spectrum: if isinstance(inc_spec, bool) or key in inc_spec: print(f" - {key}: min: {np.nanmin(res_spec[key]):.2f}, " f"max: {np.nanmax(res_spec[key]):.2f}") chi2_stat = 0 n_dof = 0 if res_phot is not None: for key, value in res_phot.items(): chi2_stat += value[1]**2 n_dof += 1 if res_spec is not None: for key, value in res_spec.items(): chi2_stat += np.sum(value[:, 1]**2) n_dof += value.shape[0] for item in parameters: if item not in ["mass", "luminosity", "distance"]: n_dof -= 1 chi2_red = chi2_stat / n_dof print(f"Reduced chi2 = {chi2_red:.2f}") print(f"Number of degrees of freedom = {n_dof}") return box.create_box( boxtype="residuals", name=objectbox.name, photometry=res_phot, spectrum=res_spec, chi2_red=chi2_red, )
def get_color_magnitude(self, object_type: Optional[str] = None ) -> box.ColorMagBox: """ Function for extracting color-magnitude data from the selected library. Parameters ---------- object_type : str, None Object type for which the colors and magnitudes are extracted. Either field dwarfs ('field') or young/low-gravity objects ('young'). All objects are selected if set to ``None``. Returns ------- species.core.box.ColorMagBox Box with the colors and magnitudes. """ if self.lib_type == "phot_lib": with h5py.File(self.database, "r") as h5_file: sptype = np.asarray( h5_file[f"photometry/{self.library}/sptype"]) parallax = np.asarray( h5_file[f"photometry/{self.library}/parallax"]) parallax_error = np.asarray( h5_file[f"photometry/{self.library}/parallax_error"]) flag = np.asarray(h5_file[f"photometry/{self.library}/flag"]) obj_names = np.asarray( h5_file[f"photometry/{self.library}/name"]) for i in range(sptype.shape[0]): if isinstance(sptype[i], bytes): sptype[i] = sptype[i].decode("utf-8") if isinstance(flag[i], bytes): flag[i] = flag[i].decode("utf-8") if isinstance(obj_names[i], bytes): obj_names[i] = obj_names[i].decode("utf-8") if object_type is None: indices = np.arange(0, np.size(sptype), 1) elif object_type == "field": indices = np.where(flag == "null")[0] elif object_type == "young": indices = [] for j, object_flag in enumerate(flag): if "young" in object_flag: indices.append(j) elif "lowg" in object_flag: indices.append(j) indices = np.array(indices) if indices.size > 0: with h5py.File(self.database, "r") as h5_file: mag1 = np.asarray(h5_file[ f"photometry/{self.library}/{self.filters_color[0]}"]) mag2 = np.asarray(h5_file[ f"photometry/{self.library}/{self.filters_color[1]}"]) else: raise ValueError( f"There is not data available from '{self.library}' for " f"'{object_type}' type objects with the chosen filters.") color = mag1 - mag2 distance = phot_util.parallax_to_distance( (parallax, parallax_error)) if self.filter_mag == self.filters_color[0]: mag, _ = phot_util.apparent_to_absolute( (mag1, None), (distance[0], distance[1])) elif self.filter_mag == self.filters_color[1]: mag, _ = phot_util.apparent_to_absolute( (mag2, None), (distance[0], distance[1])) color = color[indices] mag = mag[indices] sptype = sptype[indices] obj_names = obj_names[indices] indices = [] for i in range(color.size): if not np.isnan(color[i]) and not np.isnan(mag[i]): indices.append(i) colormag_box = box.create_box( boxtype="colormag", library=self.library, object_type=object_type, filters_color=self.filters_color, filter_mag=self.filter_mag, color=color[indices], magnitude=mag[indices], sptype=sptype[indices], names=obj_names[indices], ) elif self.lib_type == "spec_lib": read_spec_0 = read_spectrum.ReadSpectrum( spec_library=self.library, filter_name=self.filters_color[0]) read_spec_1 = read_spectrum.ReadSpectrum( spec_library=self.library, filter_name=self.filters_color[1]) read_spec_2 = read_spectrum.ReadSpectrum( spec_library=self.library, filter_name=self.filter_mag) phot_box_0 = read_spec_0.get_magnitude(sptypes=None) phot_box_1 = read_spec_1.get_magnitude(sptypes=None) phot_box_2 = read_spec_2.get_magnitude(sptypes=None) colormag_box = box.create_box( boxtype="colormag", library=self.library, object_type=object_type, filters_color=self.filters_color, filter_mag=self.filter_mag, color=phot_box_0.app_mag[:, 0] - phot_box_1.app_mag[:, 0], magnitude=phot_box_2.abs_mag[:, 0], sptype=phot_box_0.sptype, names=None, ) return colormag_box
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_color_magnitude( self, age: float, masses: np.ndarray, model: str, filters_color: Tuple[str, str], filter_mag: str, adapt_logg: bool = False, ) -> box.ColorMagBox: """ Function for calculating color-magnitude combinations from a selected isochrone. Parameters ---------- age : float Age (Myr) at which the isochrone data is interpolated. masses : np.ndarray Masses (:math:`M_\\mathrm{J}`) at which the isochrone data is interpolated. model : str Atmospheric model used to compute the synthetic photometry. filters_color : tuple(str, str) Filter names for the color as listed in the file with the isochrone data. The filter names should be provided in the format of the SVO Filter Profile Service. filter_mag : str Filter name for the absolute magnitude as listed in the file with the isochrone data. The value should be equal to one of the ``filters_color`` values. adapt_logg : bool Adapt :math:`\\log(g)` to the upper or lower boundary of the atmospheric model grid whenever the :math:`\\log(g)` that has been calculated from the isochrone mass and radius lies outside the available range of the synthetic spectra. Typically :math:`\\log(g)` has only a minor impact on the broadband magnitudes and colors. Returns ------- species.core.box.ColorMagBox Box with the color-magnitude data. """ isochrone = self.get_isochrone(age=age, masses=masses, filters_color=None, filter_mag=None) model1 = read_model.ReadModel(model=model, filter_name=filters_color[0]) model2 = read_model.ReadModel(model=model, filter_name=filters_color[1]) param_bounds = model1.get_bounds() if model1.get_parameters() == ["teff", "logg", "feh"]: if model == "sonora-bobcat": iso_feh = float(self.tag[-4:]) else: iso_feh = 0.0 elif model1.get_parameters() != ["teff", "logg"]: raise ValueError( "Creating synthetic colors and magnitudes from " "isochrones is currently only implemented for " "models with only Teff and log(g) as free parameters. " "Please contact Tomas Stolker if additional " "functionalities are required.") else: iso_feh = None mag1 = np.zeros(isochrone.masses.shape[0]) mag2 = np.zeros(isochrone.masses.shape[0]) radius = np.zeros(isochrone.masses.shape[0]) for i, mass_item in enumerate(isochrone.masses): model_param = { "teff": isochrone.teff[i], "logg": isochrone.logg[i], "mass": mass_item, "distance": 10.0, } if iso_feh is not None: model_param["feh"] = iso_feh radius[i] = read_util.get_radius(model_param["logg"], model_param["mass"]) # (Rjup) if np.isnan(isochrone.teff[i]): mag1[i] = np.nan mag2[i] = np.nan warnings.warn(f"The value of Teff is NaN for the following " f"isochrone sample: {model_param}. Setting " f"the magnitudes to NaN.") else: for item_bounds in param_bounds: if model_param[item_bounds] < param_bounds[item_bounds][0]: if adapt_logg and item_bounds == "logg": warnings.warn( f"The log(g) is {model_param[item_bounds]} but the " f"lower boundary of the model grid is " f"{param_bounds[item_bounds][0]}. Adapting " f"log(g) to {param_bounds[item_bounds][0]} since " f"adapt_logg=True.") model_param["logg"] = param_bounds["logg"][0] else: mag1[i] = np.nan mag2[i] = np.nan warnings.warn( f"The value of {item_bounds} is " f"{model_param[item_bounds]}, which is below " f"the lower bound of the model grid " f"({param_bounds[item_bounds][0]}). Setting the " f"magnitudes to NaN for the following isochrone " f"sample: {model_param}.") elif model_param[item_bounds] > param_bounds[item_bounds][ 1]: if adapt_logg and item_bounds == "logg": warnings.warn( f"The log(g) is {model_param[item_bounds]} but " f"the upper boundary of the model grid is " f"{param_bounds[item_bounds][1]}. Adapting " f"log(g) to {param_bounds[item_bounds][1]} " f"since adapt_logg=True.") model_param["logg"] = param_bounds["logg"][1] else: mag1[i] = np.nan mag2[i] = np.nan warnings.warn( f"The value of {item_bounds} is " f"{model_param[item_bounds]}, which is above " f"the upper bound of the model grid " f"({param_bounds[item_bounds][1]}). Setting the " f"magnitudes to NaN for the following isochrone " f"sample: {model_param}.") if not np.isnan(mag1[i]): mag1[i], _ = model1.get_magnitude(model_param) mag2[i], _ = model2.get_magnitude(model_param) if filter_mag == filters_color[0]: abs_mag = mag1 elif filter_mag == filters_color[1]: abs_mag = mag2 else: raise ValueError("The argument of filter_mag should be equal to " "one of the two filter values of filters_color.") return box.create_box( boxtype="colormag", library=model, object_type="model", filters_color=filters_color, filter_mag=filter_mag, color=mag1 - mag2, magnitude=abs_mag, names=None, sptype=masses, mass=masses, radius=radius, iso_tag=self.tag, )
def get_isochrone( self, age: float, masses: np.ndarray, filters_color: Optional[Tuple[str, str]] = None, filter_mag: Optional[str] = None, ) -> box.IsochroneBox: """ Function for selecting an isochrone. Parameters ---------- age : float Age (Myr) at which the isochrone data is interpolated. masses : np.ndarray Masses (:math:`M_\\mathrm{J}`) at which the isochrone data is interpolated. filters_color : tuple(str, str), None Filter names for the color as listed in the file with the isochrone data. Not selected if set to ``None`` or if only evolutionary tracks are available. filter_mag : str, None Filter name for the absolute magnitude as listed in the file with the isochrone data. Not selected if set to ``None`` or if only evolutionary tracks are available. Returns ------- species.core.box.IsochroneBox Box with the isochrone. """ age_points = np.full(masses.shape[0], age) # (Myr) color = None mag_abs = None index_teff = 2 index_logg = 4 # Read isochrone data with h5py.File(self.database, "r") as h5_file: model = h5_file[f"isochrones/{self.tag}/evolution"].attrs["model"] evolution = np.asarray(h5_file[f"isochrones/{self.tag}/evolution"]) if model == "baraffe": filters = list(h5_file[f"isochrones/{self.tag}/filters"]) magnitudes = np.asarray( h5_file[f"isochrones/{self.tag}/magnitudes"]) # Convert the h5py list of filters from bytes to strings for i, item in enumerate(filters): if isinstance(item, bytes): filters[i] = item.decode("utf-8") if model == "baraffe": if filters_color is not None: index_color_1 = filters.index(filters_color[0]) index_color_2 = filters.index(filters_color[1]) if filter_mag is not None: index_mag = filters.index(filter_mag) if filters_color is not None: mag_color_1 = griddata( points=evolution[:, 0:2], values=magnitudes[:, index_color_1], xi=np.stack((age_points, masses), axis=1), method="linear", fill_value="nan", rescale=False, ) mag_color_2 = griddata( points=evolution[:, 0:2], values=magnitudes[:, index_color_2], xi=np.stack((age_points, masses), axis=1), method="linear", fill_value="nan", rescale=False, ) color = mag_color_1 - mag_color_2 if filter_mag is not None: mag_abs = griddata( points=evolution[:, 0:2], values=magnitudes[:, index_mag], xi=np.stack((age_points, masses), axis=1), method="linear", fill_value="nan", rescale=False, ) teff = griddata( points=evolution[:, 0:2], values=evolution[:, index_teff], xi=np.stack((age_points, masses), axis=1), method="linear", fill_value="nan", rescale=False, ) logg = griddata( points=evolution[:, 0:2], values=evolution[:, index_logg], xi=np.stack((age_points, masses), axis=1), method="linear", fill_value="nan", rescale=False, ) return box.create_box( boxtype="isochrone", model=self.tag, filters_color=filters_color, filter_mag=filter_mag, color=color, magnitude=mag_abs, teff=teff, logg=logg, masses=masses, )
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: Optional[Dict[str, float]] = None, apply_mask: bool = False, spec_res: Optional[float] = None, extrapolate: bool = False, min_wavelength: Optional[float] = None, ) -> box.SpectrumBox: """ Function for selecting the calibration spectrum. Parameters ---------- model_param : dict, None Model parameters. Should contain the 'scaling' value. Not used if set to ``None``. apply_mask : bool Exclude negative values and NaN values. spec_res : float, None Spectral resolution. Original wavelength points are used if set to ``None``. extrapolate : bool Extrapolate to 6 um by fitting a power law function. min_wavelength : float, None Minimum wavelength used for fitting the power law function. All data is used if set to ``None``. Returns ------- species.core.box.SpectrumBox Box with the spectrum. """ with h5py.File(self.database, "r") as h5_file: data = np.asarray(h5_file[f"spectra/calibration/{self.tag}"]) wavelength = np.asarray(data[0, ]) flux = np.asarray(data[1, ]) error = np.asarray(data[2, ]) if apply_mask: indices = np.where(flux > 0.0)[0] wavelength = wavelength[indices] flux = flux[indices] error = error[indices] if model_param is not None: flux = model_param["scaling"] * flux error = model_param["scaling"] * error if self.wavel_range is None: wl_index = np.ones(wavelength.size, dtype=bool) else: wl_index = ((flux > 0.0) & (wavelength > self.wavel_range[0]) & (wavelength < self.wavel_range[1])) count = np.count_nonzero(wl_index) if count > 0: index = np.where(wl_index)[0] if index[0] > 0: wl_index[index[0] - 1] = True if index[-1] < len(wl_index) - 1: wl_index[index[-1] + 1] = True wavelength = wavelength[wl_index] flux = flux[wl_index] error = error[wl_index] if extrapolate: def _power_law(wavelength, offset, scaling, power_index): return offset + scaling * wavelength**power_index if min_wavelength: indices = np.where(wavelength > min_wavelength)[0] else: indices = np.arange(0, wavelength.size, 1) popt, pcov = curve_fit( f=_power_law, xdata=wavelength[indices], ydata=flux[indices], p0=(0.0, np.mean(flux[indices]), -1.0), sigma=error[indices], ) sigma = np.sqrt(np.diag(pcov)) print("Fit result for f(x) = a + b*x^c:") print(f"a = {popt[0]} +/- {sigma[0]}") print(f"b = {popt[1]} +/- {sigma[1]}") print(f"c = {popt[2]} +/- {sigma[2]}") while wavelength[-1] <= 6.0: wl_add = wavelength[-1] + wavelength[-1] / 1000.0 wavelength = np.append(wavelength, wl_add) flux = np.append(flux, _power_law(wl_add, popt[0], popt[1], popt[2])) error = np.append(error, 0.0) if spec_res is not None: wavelength_new = read_util.create_wavelengths( (wavelength[0], wavelength[-1]), spec_res) flux_new, error_new = spectres.spectres( wavelength_new, wavelength, flux, spec_errs=error, fill=0.0, verbose=True, ) wavelength = wavelength_new flux = flux_new error = error_new return box.create_box( boxtype="spectrum", spectrum="calibration", wavelength=wavelength, flux=flux, error=error, name=self.tag, )
def get_data(self, model_param: Dict[str, float]) -> box.ModelBox: """ Function for selecting a model spectrum (without interpolation) for a set of parameter values that coincide with the grid points. The stored grid points can be inspected with :func:`~species.read.read_model.ReadModel.get_points`. Parameters ---------- model_param : dict Model parameters and values. Only discrete values from the original grid are possible. Else, the nearest grid values are selected. Returns ------- species.core.box.ModelBox Box with the model spectrum. """ 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()}.') extra_param = ['radius', 'distance', 'mass', 'luminosity'] 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()}.') h5_file = self.open_database() param_key = [] param_val = [] if 'teff' in model_param: param_key.append('teff') param_val.append(model_param['teff']) if 'logg' in model_param: param_key.append('logg') param_val.append(model_param['logg']) if 'feh' in model_param: param_key.append('feh') param_val.append(model_param['feh']) if 'co' in model_param: param_key.append('co') param_val.append(model_param['co']) if 'fsed' in model_param: param_key.append('fsed') param_val.append(model_param['fsed']) flux = np.asarray(h5_file[f'models/{self.model}/flux']) indices = [] for item in param_key: data = np.asarray(h5_file[f'models/{self.model}/{item}']) data_index = np.argwhere(np.round(data, 4) == np.round(model_param[item], 4))[0] if len(data_index) == 0: raise ValueError('The parameter {item}={model_val[i]} is not found.') indices.append(data_index[0]) wl_points, wl_index = self.wavelength_points(h5_file) indices.append(wl_index) flux = flux[tuple(indices)] if 'radius' in model_param and 'distance' in model_param: scaling = (model_param['radius']*constants.R_JUP)**2 / \ (model_param['distance']*constants.PARSEC)**2 flux *= scaling h5_file.close() model_box = box.create_box(boxtype='model', model=self.model, wavelength=wl_points, flux=flux, parameters=model_param, quantity='flux') 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_color_color(self, age: float, masses: np.ndarray, model: str, filters_colors: Tuple[Tuple[str, str], Tuple[str, str]]) -> box.ColorColorBox: """ Function for calculating color-magnitude combinations from a selected isochrone. Parameters ---------- age : float Age (Myr) at which the isochrone data is interpolated. masses : np.ndarray Masses (Mjup) at which the isochrone data is interpolated. model : str Atmospheric model used to compute the synthetic photometry. filters_colors : tuple(tuple(str, str), tuple(str, str)) Filter names for the colors as listed in the file with the isochrone data. The filter names should be provided in the format of the SVO Filter Profile Service. Returns ------- species.core.box.ColorColorBox Box with the color-color data. """ isochrone = self.get_isochrone(age=age, masses=masses, filters_color=None, filter_mag=None) model1 = read_model.ReadModel(model=model, filter_name=filters_colors[0][0]) model2 = read_model.ReadModel(model=model, filter_name=filters_colors[0][1]) model3 = read_model.ReadModel(model=model, filter_name=filters_colors[1][0]) model4 = read_model.ReadModel(model=model, filter_name=filters_colors[1][1]) if model1.get_parameters() != ['teff', 'logg']: raise ValueError('Creating synthetic colors and magnitudes from isochrones is ' 'currently only implemented for models with only Teff and log(g) ' 'as free parameters. Please contact Tomas Stolker if additional ' 'functionalities are required.') mag1 = np.zeros(isochrone.masses.shape[0]) mag2 = np.zeros(isochrone.masses.shape[0]) mag3 = np.zeros(isochrone.masses.shape[0]) mag4 = np.zeros(isochrone.masses.shape[0]) for i, mass_item in enumerate(isochrone.masses): model_param = {'teff': isochrone.teff[i], 'logg': isochrone.logg[i], 'mass': mass_item, 'distance': 10.} if np.isnan(isochrone.teff[i]): mag1[i] = np.nan mag2[i] = np.nan mag3[i] = np.nan mag4[i] = np.nan warnings.warn(f'The value of Teff is NaN for the following isochrone sample: ' f'{model_param}. Setting the magnitudes to NaN.') else: for item_bounds in model1.get_bounds(): if model_param[item_bounds] <= model1.get_bounds()[item_bounds][0]: mag1[i] = np.nan mag2[i] = np.nan mag3[i] = np.nan mag4[i] = np.nan warnings.warn(f'The value of {item_bounds} is {model_param[item_bounds]}, ' f'which is below the lower bound of the model grid ' f'({model1.get_bounds()[item_bounds][0]}). Setting the ' f'magnitudes to NaN for the following isochrone sample: ' f'{model_param}.') elif model_param[item_bounds] >= model1.get_bounds()[item_bounds][1]: mag1[i] = np.nan mag2[i] = np.nan mag3[i] = np.nan mag4[i] = np.nan warnings.warn(f'The value of {item_bounds} is {model_param[item_bounds]}, ' f'which is above the upper bound of the model grid ' f'({model1.get_bounds()[item_bounds][1]}). Setting the ' f'magnitudes to NaN for the following isochrone sample: ' f'{model_param}.') if not np.isnan(mag1[i]) and not np.isnan(mag2[i]) and\ not np.isnan(mag3[i]) and not np.isnan(mag4[i]): mag1[i], _ = model1.get_magnitude(model_param) mag2[i], _ = model2.get_magnitude(model_param) mag3[i], _ = model3.get_magnitude(model_param) mag4[i], _ = model4.get_magnitude(model_param) return box.create_box(boxtype='colorcolor', library=model, object_type='model', filters=filters_colors, color1=mag1-mag2, color2=mag3-mag4, sptype=masses, names=None)
def get_isochrone(self, age: float, masses: np.ndarray, filters_color: Optional[Tuple[str, str]] = None, filter_mag: Optional[str] = None) -> box.IsochroneBox: """ Function for selecting an isochrone. Parameters ---------- age : float Age (Myr) at which the isochrone data is interpolated. masses : np.ndarray Masses (Mjup) at which the isochrone data is interpolated. filters_color : tuple(str, str), None Filter names for the color as listed in the file with the isochrone data. Not selected if set to ``None`` or if only evolutionary tracks are available. filter_mag : str, None Filter name for the absolute magnitude as listed in the file with the isochrone data. Not selected if set to ``None`` or if only evolutionary tracks are available. Returns ------- species.core.box.IsochroneBox Box with the isochrone. """ age_points = np.repeat(age, masses.shape[0]) # (Myr) color = None mag_abs = None index_teff = 2 index_logg = 4 with h5py.File(self.database, 'r') as h5_file: model = h5_file[f'isochrones/{self.tag}/evolution'].attrs['model'] evolution = np.asarray(h5_file[f'isochrones/{self.tag}/evolution']) if model == 'baraffe': filters = list(h5_file[f'isochrones/{self.tag}/filters']) magnitudes = np.asarray(h5_file[f'isochrones/{self.tag}/magnitudes']) if model == 'baraffe': if filters_color is not None: index_color_1 = filters.index(filters_color[0]) index_color_2 = filters.index(filters_color[1]) if filter_mag is not None: index_mag = filters.index(filter_mag) if filters_color is not None: mag_color_1 = griddata(points=evolution[:, 0:2], values=magnitudes[:, index_color_1], xi=np.stack((age_points, masses), axis=1), method='linear', fill_value='nan', rescale=False) mag_color_2 = griddata(points=evolution[:, 0:2], values=magnitudes[:, index_color_2], xi=np.stack((age_points, masses), axis=1), method='linear', fill_value='nan', rescale=False) color = mag_color_1-mag_color_2 if filter_mag is not None: mag_abs = griddata(points=evolution[:, 0:2], values=magnitudes[:, index_mag], xi=np.stack((age_points, masses), axis=1), method='linear', fill_value='nan', rescale=False) teff = griddata(points=evolution[:, 0:2], values=evolution[:, index_teff], xi=np.stack((age_points, masses), axis=1), method='linear', fill_value='nan', rescale=False) logg = griddata(points=evolution[:, 0:2], values=evolution[:, index_logg], xi=np.stack((age_points, masses), axis=1), method='linear', fill_value='nan', rescale=False) return box.create_box(boxtype='isochrone', model=self.tag, filters_color=filters_color, filter_mag=filter_mag, color=color, magnitude=mag_abs, teff=teff, logg=logg, masses=masses)
def get_color_color(self, object_type: Optional[str] = None ) -> box.ColorColorBox: """ Function for extracting color-color data from the selected library. Parameters ---------- object_type : str, None Object type for which the colors and magnitudes are extracted. Either field dwarfs ('field') or young/low-gravity objects ('young'). All objects are selected if set to ``None``. Returns ------- species.core.box.ColorColorBox Box with the colors. """ if self.lib_type == "phot_lib": h5_file = h5py.File(self.database, "r") sptype = np.asarray(h5_file[f"photometry/{self.library}/sptype"]) flag = np.asarray(h5_file[f"photometry/{self.library}/flag"]) obj_names = np.asarray(h5_file[f"photometry/{self.library}/name"]) if object_type is None: indices = np.arange(0, np.size(sptype), 1) elif object_type == "field": indices = np.where(flag == "null")[0] elif object_type == "young": indices = [] for j, object_flag in enumerate(flag): if "young" in object_flag: indices.append(j) elif "lowg" in object_flag: indices.append(j) indices = np.array(indices) mag1 = np.asarray(h5_file[ f"photometry/{self.library}/{self.filters_colors[0][0]}"]) mag2 = np.asarray(h5_file[ f"photometry/{self.library}/{self.filters_colors[0][1]}"]) mag3 = np.asarray(h5_file[ f"photometry/{self.library}/{self.filters_colors[1][0]}"]) mag4 = np.asarray(h5_file[ f"photometry/{self.library}/{self.filters_colors[1][1]}"]) color1 = mag1 - mag2 color2 = mag3 - mag4 color1 = color1[indices] color2 = color2[indices] sptype = sptype[indices] obj_names = obj_names[indices] indices = [] for i in range(color1.size): if not np.isnan(color1[i]) and not np.isnan(color2[i]): indices.append(i) colorbox = box.create_box( boxtype="colorcolor", library=self.library, object_type=object_type, filters=self.filters_colors, color1=color1[indices], color2=color2[indices], sptype=sptype[indices], names=obj_names[indices], ) h5_file.close() elif self.lib_type == "spec_lib": read_spec_0 = read_spectrum.ReadSpectrum( spec_library=self.library, filter_name=self.filters_colors[0][0]) read_spec_1 = read_spectrum.ReadSpectrum( spec_library=self.library, filter_name=self.filters_colors[0][1]) read_spec_2 = read_spectrum.ReadSpectrum( spec_library=self.library, filter_name=self.filters_colors[1][0]) read_spec_3 = read_spectrum.ReadSpectrum( spec_library=self.library, filter_name=self.filters_colors[1][1]) phot_box_0 = read_spec_0.get_magnitude(sptypes=None) phot_box_1 = read_spec_1.get_magnitude(sptypes=None) phot_box_2 = read_spec_2.get_magnitude(sptypes=None) phot_box_3 = read_spec_3.get_magnitude(sptypes=None) colorbox = box.create_box( boxtype="colorcolor", library=self.library, object_type=object_type, filters=self.filters_colors, color1=phot_box_0.app_mag[:, 0] - phot_box_1.app_mag[:, 0], color2=phot_box_2.app_mag[:, 0] - phot_box_3.app_mag[:, 0], sptype=phot_box_0.sptype, names=None, ) return colorbox
def get_color_color( self, age: float, masses: np.ndarray, model: str, filters_colors: Tuple[Tuple[str, str], Tuple[str, str]], ) -> box.ColorColorBox: """ Function for calculating color-magnitude combinations from a selected isochrone. Parameters ---------- age : float Age (Myr) at which the isochrone data is interpolated. masses : np.ndarray Masses (:math:`M_\\mathrm{J}`) at which the isochrone data is interpolated. model : str Atmospheric model used to compute the synthetic photometry. filters_colors : tuple(tuple(str, str), tuple(str, str)) Filter names for the colors as listed in the file with the isochrone data. The filter names should be provided in the format of the SVO Filter Profile Service. Returns ------- species.core.box.ColorColorBox Box with the color-color data. """ isochrone = self.get_isochrone(age=age, masses=masses, filters_color=None, filter_mag=None) model1 = read_model.ReadModel(model=model, filter_name=filters_colors[0][0]) model2 = read_model.ReadModel(model=model, filter_name=filters_colors[0][1]) model3 = read_model.ReadModel(model=model, filter_name=filters_colors[1][0]) model4 = read_model.ReadModel(model=model, filter_name=filters_colors[1][1]) if model1.get_parameters() == ["teff", "logg", "feh"]: if model == "sonora-bobcat": iso_feh = float(self.tag[-4:]) else: iso_feh = 0.0 elif model1.get_parameters() != ["teff", "logg"]: raise ValueError( "Creating synthetic colors and magnitudes from " "isochrones is currently only implemented for " "models with only Teff and log(g) as free parameters. " "Please contact Tomas Stolker if additional " "functionalities are required.") else: iso_feh = None mag1 = np.zeros(isochrone.masses.shape[0]) mag2 = np.zeros(isochrone.masses.shape[0]) mag3 = np.zeros(isochrone.masses.shape[0]) mag4 = np.zeros(isochrone.masses.shape[0]) radius = np.zeros(isochrone.masses.shape[0]) for i, mass_item in enumerate(isochrone.masses): model_param = { "teff": isochrone.teff[i], "logg": isochrone.logg[i], "mass": mass_item, "distance": 10.0, } if iso_feh is not None: model_param["feh"] = iso_feh radius[i] = read_util.get_radius(model_param["logg"], model_param["mass"]) # (Rjup) if np.isnan(isochrone.teff[i]): mag1[i] = np.nan mag2[i] = np.nan mag3[i] = np.nan mag4[i] = np.nan warnings.warn( f"The value of Teff is NaN for the following isochrone " f"sample: {model_param}. Setting the magnitudes to NaN.") else: for item_bounds in model1.get_bounds(): if model_param[item_bounds] < model1.get_bounds( )[item_bounds][0]: mag1[i] = np.nan mag2[i] = np.nan mag3[i] = np.nan mag4[i] = np.nan warnings.warn( f"The value of {item_bounds} is " f"{model_param[item_bounds]}, which is " f"below the lower bound of the model grid " f" ({model1.get_bounds()[item_bounds][0]}). " f"Setting the magnitudes to NaN for the " f"following isochrone sample: {model_param}.") elif model_param[item_bounds] > model1.get_bounds( )[item_bounds][1]: mag1[i] = np.nan mag2[i] = np.nan mag3[i] = np.nan mag4[i] = np.nan warnings.warn( f"The value of {item_bounds} is " f"{model_param[item_bounds]}, which is above " f"the upper bound of the model grid " f"({model1.get_bounds()[item_bounds][1]}). " f"Setting the magnitudes to NaN for the " f"following isochrone sample: {model_param}.") if (not np.isnan(mag1[i]) and not np.isnan(mag2[i]) and not np.isnan(mag3[i]) and not np.isnan(mag4[i])): mag1[i], _ = model1.get_magnitude(model_param) mag2[i], _ = model2.get_magnitude(model_param) mag3[i], _ = model3.get_magnitude(model_param) mag4[i], _ = model4.get_magnitude(model_param) return box.create_box( boxtype="colorcolor", library=model, object_type="model", filters=filters_colors, color1=mag1 - mag2, color2=mag3 - mag4, names=None, sptype=masses, mass=masses, radius=radius, iso_tag=self.tag, )
def get_color_magnitude(self, object_type: Optional[str] = None) -> box.ColorMagBox: """ Function for extracting color-magnitude data from the selected library. Parameters ---------- object_type : str, None Object type for which the colors and magnitudes are extracted. Either field dwarfs ('field') or young/low-gravity objects ('young'). All objects are selected if set to ``None``. Returns ------- species.core.box.ColorMagBox Box with the colors and magnitudes. """ if self.lib_type == 'phot_lib': with h5py.File(self.database, 'r') as h5_file: sptype = np.asarray(h5_file[f'photometry/{self.library}/sptype']) dist = np.asarray(h5_file[f'photometry/{self.library}/distance']) dist_error = np.asarray(h5_file[f'photometry/{self.library}/distance_error']) flag = np.asarray(h5_file[f'photometry/{self.library}/flag']) obj_names = np.asarray(h5_file[f'photometry/{self.library}/name']) if object_type is None: indices = np.arange(0, np.size(sptype), 1) elif object_type == 'field': indices = np.where(flag == 'null')[0] elif object_type == 'young': indices = [] for j, object_flag in enumerate(flag): if 'young' in object_flag: indices.append(j) elif 'lowg' in object_flag: indices.append(j) indices = np.array(indices) if indices.size > 0: with h5py.File(self.database, 'r') as h5_file: mag1 = np.asarray(h5_file[f'photometry/{self.library}/{self.filters_color[0]}']) mag2 = np.asarray(h5_file[f'photometry/{self.library}/{self.filters_color[1]}']) else: raise ValueError(f'There is not data available from \'{self.library}\' for ' f'\'{object_type}\' type objects with the chosen filters.') color = mag1 - mag2 if self.filter_mag == self.filters_color[0]: mag, _ = phot_util.apparent_to_absolute((mag1, None), (dist, dist_error)) elif self.filter_mag == self.filters_color[1]: mag, _ = phot_util.apparent_to_absolute((mag2, None), (dist, dist_error)) color = color[indices] mag = mag[indices] sptype = sptype[indices] obj_names = obj_names[indices] indices = [] for i in range(color.size): if not np.isnan(color[i]) and not np.isnan(mag[i]): indices.append(i) colormag_box = box.create_box(boxtype='colormag', library=self.library, object_type=object_type, filters_color=self.filters_color, filter_mag=self.filter_mag, color=color[indices], magnitude=mag[indices], sptype=sptype[indices], names=obj_names[indices]) elif self.lib_type == 'spec_lib': read_spec_0 = read_spectrum.ReadSpectrum(spec_library=self.library, filter_name=self.filters_color[0]) read_spec_1 = read_spectrum.ReadSpectrum(spec_library=self.library, filter_name=self.filters_color[1]) read_spec_2 = read_spectrum.ReadSpectrum(spec_library=self.library, filter_name=self.filter_mag) phot_box_0 = read_spec_0.get_magnitude(sptypes=None) phot_box_1 = read_spec_1.get_magnitude(sptypes=None) phot_box_2 = read_spec_2.get_magnitude(sptypes=None) colormag_box = box.create_box(boxtype='colormag', library=self.library, object_type=object_type, filters_color=self.filters_color, filter_mag=self.filter_mag, color=phot_box_0.app_mag[:, 0]-phot_box_1.app_mag[:, 0], magnitude=phot_box_2.abs_mag[:, 0], sptype=phot_box_0.sptype, names=None) return colormag_box
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 get_residuals(datatype: str, spectrum: str, parameters: Dict[str, float], objectbox: box.ObjectBox, inc_phot: Union[bool, List[str]] = True, inc_spec: Union[bool, List[str]] = True, **kwargs_radtrans: Optional[dict]) -> box.ResidualsBox: """ Parameters ---------- datatype : str Data type ('model' or 'calibration'). spectrum : str Name of the atmospheric model or calibration spectrum. parameters : dict Parameters and values for the spectrum objectbox : species.core.box.ObjectBox Box with the photometry and/or spectra of an object. A scaling and/or error inflation of the spectra should be applied with :func:`~species.util.read_util.update_spectra` beforehand. inc_phot : bool, list(str) Include photometric data in the fit. If a boolean, either all (``True``) or none (``False``) of the data are selected. If a list, a subset of filter names (as stored in the database) can be provided. inc_spec : bool, list(str) Include spectroscopic data in the fit. If a boolean, either all (``True``) or none (``False``) of the data are selected. If a list, a subset of spectrum names (as stored in the database with :func:`~species.data.database.Database.add_object`) can be provided. Keyword arguments ----------------- kwargs_radtrans : dict Dictionary with the keyword arguments for the ``ReadRadtrans`` object, containing ``line_species``, ``cloud_species``, and ``scattering``. Returns ------- species.core.box.ResidualsBox Box with the residuals. """ if 'filters' in kwargs_radtrans: warnings.warn('The \'filters\' parameter has been deprecated. Please use the \'inc_phot\' ' 'parameter instead. The \'filters\' parameter is ignored.') if isinstance(inc_phot, bool) and inc_phot: inc_phot = objectbox.filters if inc_phot: model_phot = multi_photometry(datatype=datatype, spectrum=spectrum, filters=inc_phot, parameters=parameters) res_phot = {} for item in inc_phot: transmission = read_filter.ReadFilter(item) res_phot[item] = np.zeros(objectbox.flux[item].shape) if objectbox.flux[item].ndim == 1: res_phot[item][0] = transmission.mean_wavelength() res_phot[item][1] = (objectbox.flux[item][0]-model_phot.flux[item]) / \ objectbox.flux[item][1] elif objectbox.flux[item].ndim == 2: for j in range(objectbox.flux[item].shape[1]): res_phot[item][0, j] = transmission.mean_wavelength() res_phot[item][1, j] = (objectbox.flux[item][0, j]-model_phot.flux[item]) / \ objectbox.flux[item][1, j] else: res_phot = None if inc_spec: res_spec = {} readmodel = None for key in objectbox.spectrum: if isinstance(inc_spec, bool) or key in inc_spec: wavel_range = (0.9*objectbox.spectrum[key][0][0, 0], 1.1*objectbox.spectrum[key][0][-1, 0]) wl_new = objectbox.spectrum[key][0][:, 0] spec_res = objectbox.spectrum[key][3] if spectrum == 'planck': readmodel = read_planck.ReadPlanck(wavel_range=wavel_range) model = readmodel.get_spectrum(model_param=parameters, spec_res=1000.) flux_new = spectres.spectres(wl_new, model.wavelength, model.flux, spec_errs=None, fill=0., verbose=True) else: if spectrum == 'petitradtrans': # TODO change back pass # radtrans = read_radtrans.ReadRadtrans(line_species=kwargs_radtrans['line_species'], # cloud_species=kwargs_radtrans['cloud_species'], # scattering=kwargs_radtrans['scattering'], # wavel_range=wavel_range) # # model = radtrans.get_model(parameters, spec_res=None) # # # separate resampling to the new wavelength points # # flux_new = spectres.spectres(wl_new, # model.wavelength, # model.flux, # spec_errs=None, # fill=0., # verbose=True) else: readmodel = read_model.ReadModel(spectrum, wavel_range=wavel_range) # resampling to the new wavelength points is done in teh get_model function model_spec = readmodel.get_model(parameters, spec_res=spec_res, wavel_resample=wl_new, smooth=True) flux_new = model_spec.flux data_spec = objectbox.spectrum[key][0] res_tmp = (data_spec[:, 1]-flux_new) / data_spec[:, 2] res_spec[key] = np.column_stack([wl_new, res_tmp]) else: res_spec = None print('Calculating residuals... [DONE]') print('Residuals (sigma):') if res_phot is not None: for item in inc_phot: if res_phot[item].ndim == 1: print(f' - {item}: {res_phot[item][1]:.2f}') elif res_phot[item].ndim == 2: for j in range(res_phot[item].shape[1]): print(f' - {item}: {res_phot[item][1, j]:.2f}') if res_spec is not None: for key in objectbox.spectrum: if isinstance(inc_spec, bool) or key in inc_spec: print(f' - {key}: min: {np.nanmin(res_spec[key]):.2f}, ' f'max: {np.nanmax(res_spec[key]):.2f}') return box.create_box(boxtype='residuals', name=objectbox.name, photometry=res_phot, spectrum=res_spec)