Example #1
0
    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,
        )
Example #2
0
    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
Example #3
0
    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,
        )
Example #4
0
    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
Example #5
0
    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
Example #6
0
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()
Example #7
0
    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)