Beispiel #1
0
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
Beispiel #2
0
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)
Beispiel #3
0
    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)
Beispiel #4
0
    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,
        )
Beispiel #5
0
    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,
        )
Beispiel #6
0
    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,
        )
Beispiel #7
0
    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)
Beispiel #8
0
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)
Beispiel #9
0
    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)
Beispiel #10
0
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
Beispiel #11
0
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)
Beispiel #12
0
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)
Beispiel #13
0
    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)
Beispiel #14
0
    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)
Beispiel #15
0
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)
Beispiel #16
0
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,
    )
Beispiel #17
0
    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
Beispiel #18
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
Beispiel #19
0
    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,
        )
Beispiel #20
0
    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,
        )
Beispiel #21
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,
        )
Beispiel #22
0
    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,
        )
Beispiel #23
0
    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
Beispiel #24
0
    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)
Beispiel #25
0
    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)
Beispiel #26
0
    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
Beispiel #27
0
    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,
        )
Beispiel #28
0
    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
Beispiel #29
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
Beispiel #30
0
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)