示例#1
0
def add_luminosity(modelbox):
    """
    Function to add the luminosity of a model spectrum to the parameter dictionary of the box. The
    luminosity is by default calculated at a spectral resolution of 1000.

    Parameters
    ----------
    modelbox : species.core.box.ModelBox
        Box with the model spectrum. Should also contain the dictionary with the model parameters,
        the radius in particular.

    Returns
    -------
    species.core.box.ModelBox
        The input box with the luminosity added in the parameter dictionary.
    """

    readmodel = read_model.ReadModel(model=modelbox.model,
                                     wavelength=None,
                                     teff=None)
    fullspec = readmodel.get_model(model_par=modelbox.parameters)

    flux = simps(fullspec.flux, fullspec.wavelength)

    if 'distance' in modelbox.parameters:
        luminosity = 4. * math.pi * (fullspec.parameters['distance'] *
                                     constants.PARSEC)**2 * flux  # [W]
    else:
        luminosity = 4. * math.pi * (fullspec.parameters['radius'] *
                                     constants.R_JUP)**2 * flux  # [W]

    modelbox.parameters['luminosity'] = luminosity / constants.L_SUN  # [Lsun]

    return modelbox
示例#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)
示例#3
0
def add_luminosity(modelbox):
    """
    Function to add the luminosity of a model spectrum to the parameter dictionary of the box.

    Parameters
    ----------
    modelbox : species.core.box.ModelBox
        Box with the model spectrum. Should also contain the dictionary with the model parameters,
        the radius in particular.

    Returns
    -------
    species.core.box.ModelBox
        The input box with the luminosity added in the parameter dictionary.
    """

    print('Calculating the luminosity...', end='', flush=True)

    if modelbox.model == 'planck':
        readmodel = read_planck.ReadPlanck(wavel_range=(1e-1, 1e3))
        fullspec = readmodel.get_spectrum(model_param=modelbox.parameters,
                                          spec_res=1000.)

    else:
        readmodel = read_model.ReadModel(modelbox.model)
        fullspec = readmodel.get_model(modelbox.parameters)

    flux = simps(fullspec.flux, fullspec.wavelength)

    if 'distance' in modelbox.parameters:
        luminosity = 4. * np.pi * (fullspec.parameters['distance'] *
                                   constants.PARSEC)**2 * flux  # (W)

        # Analytical solution for a single-component Planck function
        # luminosity = 4.*np.pi*(modelbox.parameters['radius']*constants.R_JUP)**2* \
        #     constants.SIGMA_SB*modelbox.parameters['teff']**4.

    else:
        luminosity = 4. * np.pi * (fullspec.parameters['radius'] *
                                   constants.R_JUP)**2 * flux  # (W)

    modelbox.parameters['luminosity'] = luminosity / constants.L_SUN  # (Lsun)

    print(' [DONE]')

    print(f'Wavelength range (um): {fullspec.wavelength[0]:.2e} - '
          f'{fullspec.wavelength[-1]:.2e}')

    print(f'Luminosity (Lsun): {luminosity/constants.L_SUN:.2e}')

    return modelbox
示例#4
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)
示例#5
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)
示例#6
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,
    )
示例#7
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)
示例#8
0
    def get_mcmc_spectra(self, tag, burnin, random, wavelength, specres=None):
        """
        Parameters
        ----------
        tag : str
            Database tag with the MCMC samples.
        burnin : int
            Number of burnin steps.
        random : int
            Number of random samples.
        wavelength : tuple(float, float) or str
            Wavelength range (micron) or filter name. Full spectrum if set to None.
        specres : float
            Spectral resolution, achieved by smoothing with a Gaussian kernel. The original
            wavelength points are used if set to None.

        Returns
        -------
        tuple(species.core.box.ModelBox, )
            Boxes with the randomly sampled spectra.
        """

        sys.stdout.write('Getting MCMC spectra...')
        sys.stdout.flush()

        h5_file = h5py.File(self.database, 'r')
        dset = h5_file['results/mcmc/' + tag]

        nparam = dset.attrs['nparam']
        spectrum_type = dset.attrs['type']
        spectrum_name = dset.attrs['spectrum']

        if specres is not None and spectrum_type == 'calibration':
            warnings.warn(
                "Smoothing of the spectral resolution is not implemented for calibration "
                "spectra.")

        if dset.attrs.__contains__('distance'):
            distance = dset.attrs['distance']
        else:
            distance = None

        samples = np.asarray(dset)
        samples = samples[:, burnin:, :]

        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 = []
        for i in range(nparam):
            param.append(str(dset.attrs['parameter' + str(i)]))

        if spectrum_type == 'model':
            readmodel = read_model.ReadModel(spectrum_name, wavelength)
        elif spectrum_type == 'calibration':
            readcalib = read_calibration.ReadCalibration(spectrum_name, None)

        boxes = []

        progbar = progress.bar.Bar('\rGetting MCMC spectra...',
                                   max=samples.shape[0],
                                   suffix='%(percent)d%%')

        for i in range(samples.shape[0]):
            model_par = {}
            for j in range(samples.shape[1]):
                model_par[param[j]] = samples[i, j]

            if distance:
                model_par['distance'] = distance

            if spectrum_type == 'model':
                specbox = readmodel.get_model(model_par, specres)
            elif spectrum_type == 'calibration':
                specbox = readcalib.get_spectrum(model_par)

            box.type = 'mcmc'

            boxes.append(specbox)

            progbar.next()

        progbar.finish()

        h5_file.close()

        return tuple(boxes)
示例#9
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,
        )
示例#10
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)
示例#11
0
def get_residuals(datatype,
                  spectrum,
                  parameters,
                  filters,
                  objectbox,
                  inc_phot=True,
                  inc_spec=False):
    """
    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
    filters : tuple(str, )
        Filter IDs. All available photometry of the object is used if set to None.
    objectbox : species.core.box.ObjectBox
        Box with the photometry and/or spectrum of an object.
    inc_phot : bool
        Include photometry.
    inc_spec : bool
        Include spectrum.

    Returns
    -------
    species.core.box.ResidualsBox
        Box with the photometry and/or spectrum residuals.
    """

    if filters is None:
        filters = objectbox.filter

    if inc_phot:
        model_phot = multi_photometry(datatype=datatype,
                                      spectrum=spectrum,
                                      filters=filters,
                                      parameters=parameters)

        res_phot = np.zeros((2, len(objectbox.flux)))

        for i, item in enumerate(filters):
            transmission = read_filter.ReadFilter(item)

            res_phot[0, i] = transmission.mean_wavelength()
            res_phot[1, i] = (objectbox.flux[item][0] -
                              model_phot.flux[item]) / objectbox.flux[item][1]

    else:
        res_phot = None

    sys.stdout.write('Calculating residuals...')
    sys.stdout.flush()

    if inc_spec:
        wl_range = (0.9 * objectbox.spectrum[0, 0],
                    1.1 * objectbox.spectrum[-1, 0])

        readmodel = read_model.ReadModel(spectrum, wl_range)
        model = readmodel.get_model(parameters)

        wl_new = objectbox.spectrum[:, 0]

        flux_new = spectres.spectres(new_spec_wavs=wl_new,
                                     old_spec_wavs=model.wavelength,
                                     spec_fluxes=model.flux,
                                     spec_errs=None)

        res_spec = np.zeros((2, objectbox.spectrum.shape[0]))

        res_spec[0, :] = wl_new
        res_spec[1, :] = (objectbox.spectrum[:, 1] -
                          flux_new) / objectbox.spectrum[:, 2]

    else:
        res_spec = None

    sys.stdout.write(' [DONE]\n')
    sys.stdout.flush()

    return box.create_box(boxtype='residuals',
                          name=objectbox.name,
                          photometry=res_phot,
                          spectrum=res_spec)
示例#12
0
def add_luminosity(modelbox):
    """
    Function to add the luminosity of a model spectrum to the parameter
    dictionary of the box.

    Parameters
    ----------
    modelbox : species.core.box.ModelBox
        Box with the model spectrum. Should also contain the dictionary
        with the model parameters, the radius in particular.

    Returns
    -------
    species.core.box.ModelBox
        The input box with the luminosity added in the parameter
        dictionary.
    """

    print("Calculating the luminosity...", end="", flush=True)

    if modelbox.model == "planck":
        readmodel = read_planck.ReadPlanck(wavel_range=(1e-1, 1e3))
        fullspec = readmodel.get_spectrum(model_param=modelbox.parameters,
                                          spec_res=1000.0)

    else:
        readmodel = read_model.ReadModel(modelbox.model)
        fullspec = readmodel.get_model(modelbox.parameters)

    flux = simps(fullspec.flux, fullspec.wavelength)

    if "parallax" in modelbox.parameters:
        luminosity = (
            4.0 * np.pi *
            (1e3 * constants.PARSEC / fullspec.parameters["parallax"])**2 *
            flux)  # (W)

    elif "distance" in modelbox.parameters:
        luminosity = (4.0 * np.pi *
                      (fullspec.parameters["distance"] * constants.PARSEC)**2 *
                      flux)  # (W)

        # Analytical solution for a single-component Planck function
        # luminosity = 4.*np.pi*(modelbox.parameters['radius']*constants.R_JUP)**2* \
        #     constants.SIGMA_SB*modelbox.parameters['teff']**4.

    else:
        luminosity = (4.0 * np.pi *
                      (fullspec.parameters["radius"] * constants.R_JUP)**2 *
                      flux)  # (W)

    modelbox.parameters["luminosity"] = luminosity / constants.L_SUN  # (Lsun)

    print(" [DONE]")

    print(f"Wavelength range (um): {fullspec.wavelength[0]:.2e} - "
          f"{fullspec.wavelength[-1]:.2e}")

    print(f"Luminosity (Lsun): {luminosity/constants.L_SUN:.2e}")

    return modelbox
示例#13
0
def update_objectbox(objectbox: box.ObjectBox,
                     model_param: Dict[str, float],
                     model: Optional[str] = None) -> box.ObjectBox:
    """
    Function for updating the spectra and/or photometric fluxes in
    an :class:`~species.core.box.ObjectBox`, for example by applying
    a flux scaling and/or error inflation.

    Parameters
    ----------
    objectbox : species.core.box.ObjectBox
        Box with the object's data, including the spectra and/or
        photometric fluxes.
    model_param : dict
        Dictionary with the model parameters. Should contain the
        value(s) of the flux scaling and/or the error inflation.
    model : str, None
        Name of the atmospheric model. Only required for inflating the
        errors of spectra. Otherwise, the argument can be set to
        ``None``. Not required when ``model='petitradtrans'`` because
        the error inflation is differently implemented with
        :class:`~species.analysis.retrieval.AtmosphericRetrieval`.

    Returns
    -------
    species.core.box.ObjectBox
        The input box which includes the spectra with the scaled fluxes
        and/or inflated errors.
    """

    if objectbox.flux is not None:

        for key, value in objectbox.flux.items():
            instr_name = key.split(".")[0]

            if f"{key}_error" in model_param:
                # Inflate photometric error of filter
                var_add = model_param[f"{key}_error"]**2 * value[0]**2

            elif f"{instr_name}_error" in model_param:
                # Inflate photometric error of instrument
                var_add = model_param[f"{instr_name}_error"]**2 * value[0]**2

            else:
                # No inflation required
                var_add = None

            if var_add is not None:
                message = (f"Inflating the error of {key} " +
                           f"(W m-2 um-1): {np.sqrt(var_add):.2e}...")

                print(message, end="", flush=True)

                value[1] = np.sqrt(value[1]**2 + var_add)

                print(" [DONE]")

                objectbox.flux[key] = value

    if objectbox.spectrum is not None:
        # Check if there are any spectra

        for key, value in objectbox.spectrum.items():
            # Get the spectrum (3 columns)
            spec_tmp = value[0]

            if f"scaling_{key}" in model_param:
                # Scale the flux of the spectrum
                scaling = model_param[f"scaling_{key}"]

                print(f"Scaling the flux of {key}: {scaling:.2f}...",
                      end="",
                      flush=True)
                spec_tmp[:, 1] *= model_param[f"scaling_{key}"]
                print(" [DONE]")

            if f"error_{key}" in model_param:
                if model is None:
                    warnings.warn(
                        f"The dictionary with model parameters contains the error "
                        f"inflation for {key} but the argument of 'model' is set "
                        f"to None. Inflation of the errors is therefore not possible."
                    )

                elif model == "petitradtrans":
                    # Increase the errors by a constant value
                    add_error = 10.0**model_param[f"error_{key}"]
                    log_msg = (
                        f"Inflating the error of {key} (W m-2 um-1): {add_error:.2e}..."
                    )

                    print(log_msg, end="", flush=True)
                    spec_tmp[:, 2] += add_error
                    print(" [DONE]")

                else:
                    # Calculate the model spectrum
                    wavel_range = (0.9 * spec_tmp[0, 0], 1.1 * spec_tmp[-1, 0])
                    readmodel = read_model.ReadModel(model,
                                                     wavel_range=wavel_range)

                    model_box = readmodel.get_model(
                        model_param,
                        spec_res=value[3],
                        wavel_resample=spec_tmp[:, 0],
                        smooth=True,
                    )

                    # Scale the errors relative to the model spectrum
                    err_scaling = model_param[f"error_{key}"]
                    log_msg = f"Inflating the error of {key}: {err_scaling:.2e}..."

                    print(log_msg, end="", flush=True)
                    spec_tmp[:, 2] = np.sqrt(spec_tmp[:, 2]**2 +
                                             (err_scaling * model_box.flux)**2)
                    print(" [DONE]")

            # Store the spectra with the scaled fluxes and/or errors
            # The other three elements (i.e. the covariance matrix,
            # the inverted covariance matrix, and the spectral
            # resolution) remain unaffected
            objectbox.spectrum[key] = (spec_tmp, value[1], value[2], value[3])

    return objectbox
示例#14
0
    def get_mcmc_photometry(self, tag, burnin, filter_id):
        """
        Parameters
        ----------
        tag : str
            Database tag with the MCMC samples.
        burnin : int
            Number of burnin steps.
        filter_id : str
            Filter ID for which the photometry is calculated.

        Returns
        -------
        numpy.ndarray
            Synthetic photometry (mag).
        """

        h5_file = h5py.File(self.database, 'r')
        dset = h5_file['results/mcmc/' + tag]

        nparam = dset.attrs['nparam']
        spectrum_type = dset.attrs['type']
        spectrum_name = dset.attrs['spectrum']

        if dset.attrs.__contains__('distance'):
            distance = dset.attrs['distance']
        else:
            distance = None

        samples = np.asarray(dset)
        samples = samples[:, burnin:, :]
        samples = samples.reshape(
            (samples.shape[0] * samples.shape[1], nparam))

        param = []
        for i in range(nparam):
            param.append(str(dset.attrs['parameter' + str(i)]))

        h5_file.close()

        if spectrum_type == 'model':
            readmodel = read_model.ReadModel(spectrum_name, filter_id)
        # elif spectrum_type == 'calibration':
        #     readcalib = read_calibration.ReadCalibration(spectrum_name, None)

        mcmc_phot = np.zeros((samples.shape[0], 1))

        progbar = progress.bar.Bar('Getting MCMC photometry...',
                                   max=samples.shape[0],
                                   suffix='%(percent)d%%')

        for i in range(samples.shape[0]):
            model_par = {}
            for j in range(nparam):
                model_par[param[j]] = samples[i, j]

            if distance:
                model_par['distance'] = distance

            if spectrum_type == 'model':
                mcmc_phot[i, 0], _ = readmodel.get_magnitude(model_par)
            # elif spectrum_type == 'calibration':
            #     specbox = readcalib.get_spectrum(model_par)

            progbar.next()

        progbar.finish()

        return mcmc_phot
示例#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)
示例#16
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)
示例#17
0
    def __init__(self,
                 object_name: str,
                 model: str,
                 bounds: Dict[str, Union[Tuple[float, float],
                                         Tuple[Optional[Tuple[float, float]],
                                               Optional[Tuple[float, float]]],
                                         List[Tuple[float, float]]]],
                 inc_phot: Union[bool, List[str]] = True,
                 inc_spec: Union[bool, List[str]] = True,
                 fit_corr: Optional[List[str]] = None) -> None:
        """
        The grid of spectra is linearly interpolated for each photometric point and spectrum while
        taking into account the filter profile, spectral resolution, and wavelength sampling.
        Therefore, when fitting spectra from a model grid, the computation time of the
        interpolation will depend on the wavelength range, spectral resolution, and parameter
        space of the spectra that are stored in the database.

        Parameters
        ----------
        object_name : str
            Object name as stored in the database with
            :func:`~species.data.database.Database.add_object` or
            :func:`~species.data.database.Database.add_companion`.
        model : str
            Atmospheric model (e.g. 'bt-settl', 'exo-rem', or 'planck').
        bounds : dict(str, tuple(float, float)), None
            The boundaries that are used for the uniform priors.

            Atmospheric model parameters (e.g. ``model='bt-settl'``):

                 - Boundaries are provided as tuple of two floats. For example,
                   ``bounds={'teff': (1000, 1500.), 'logg': (3.5, 5.), 'radius': (0.8, 1.2)}``.

                 - The grid boundaries are used if set to ``None``. For example,
                   ``bounds={'teff': None, 'logg': None}``. The radius range is set to
                   0.8-1.5 Rjup if the boundary is set to None.

            Blackbody emission parameters (``model='planck'``):

                 - Parameter boundaries have to be provided for 'teff' and 'radius'.

                 - For a single blackbody component, the values are provided as a tuple with two
                   floats. For example, ``bounds={'teff': (1000., 2000.), 'radius': (0.8, 1.2)}``.

                 - For multiple blackbody component, the values are provided as a list with tuples.
                   For example, ``bounds={'teff': [(1000., 1400.), (1200., 1600.)],
                   'radius': [(0.8, 1.5), (1.2, 2.)]}``.

                 - When fitting multiple blackbody components, a prior is used which restricts the
                   temperatures and radii to decreasing and increasing values, respectively, in the
                   order as provided in ``bounds``.

            Calibration parameters:

                 - For each spectrum/instrument, two optional parameters can be fitted to account
                   for biases in the calibration: a scaling of the flux and a constant inflation of
                   the uncertainties.

                 - For example, ``bounds={'SPHERE': ((0.8, 1.2), (-18., -14.))}`` if the scaling is
                   fitted between 0.8 and 1.2, and the error is inflated with a value between 1e-18
                   and 1e-14 W m-2 um-1.

                 - The dictionary key should be equal to the database tag of the spectrum. For
                   example, ``{'SPHERE': ((0.8, 1.2), (-18., -14.))}`` if the spectrum is stored as
                   ``'SPHERE'`` with :func:`~species.data.database.Database.add_object`.

                 - Each of the two calibration parameters can be set to ``None`` in which case the
                   parameter is not used. For example, ``bounds={'SPHERE': ((0.8, 1.2), None)}``.

                 - No calibration parameters are fitted if the spectrum name is not included in
                   ``bounds``.

            ISM extinction parameters:

                 - There are three approaches for fitting extinction. The first is with the
                   empirical relation from Cardelli et al. (1989) for ISM extinction.

                 - The extinction is parametrized by the V band extinction, A_V (``ism_ext``), and
                   the reddening, R_V (``ism_red``).

                 - The prior boundaries of ``ism_ext`` and ``ext_red`` should be provided in the
                   ``bounds`` dictionary, for example ``bounds={'ism_ext': (0., 10.),
                   'ism_red': (0., 20.)}``.

                 - Only supported by ``run_multinest``.

            Log-normal size distribution:

                 - The second approach is fitting the extinction of a log-normal size distribution
                   of grains with a crystalline MgSiO3 composition, and a homogeneous, spherical
                   structure.

                 - The size distribution is parameterized with a mean geometric radius
                   (``lognorm_radius`` in um) and a geometric standard deviation
                   (``lognorm_sigma``, dimensionless).

                 - The extinction (``lognorm_ext``) is fitted in the V band (A_V in mag) and the
                   wavelength-dependent extinction cross sections are interpolated from a
                   pre-tabulated grid.

                 - The prior boundaries of ``lognorm_radius``, ``lognorm_sigma``, and
                   ``lognorm_ext`` should be provided in the ``bounds`` dictionary, for example
                   ``bounds={'lognorm_radius': (0.01, 10.), 'lognorm_sigma': (1.2, 10.),
                   'lognorm_ext': (0., 5.)}``.

                 - A uniform prior is used for ``lognorm_sigma`` and ``lognorm_ext``, and a
                   log-uniform prior for ``lognorm_radius``.

                 - Only supported by ``run_multinest``.

            Power-law size distribution:

                 - The third approach is fitting the extinction of a power-law size distribution
                   of grains, again with a crystalline MgSiO3 composition, and a homogeneous,
                   spherical structure.

                 - The size distribution is parameterized with a maximum radius (``powerlaw_max``
                   in um) and a power-law exponent (``powerlaw_exp``, dimensionless). The
                   minimum radius is fixed to 1 nm.

                 - The extinction (``powerlaw_ext``) is fitted in the V band (A_V in mag) and the
                   wavelength-dependent extinction cross sections are interpolated from a
                   pre-tabulated grid.

                 - The prior boundaries of ``powerlaw_max``, ``powerlaw_exp``, and ``powerlaw_ext``
                   should be provided in the ``bounds`` dictionary, for example ``'powerlaw_max':
                   (0.5, 10.), 'powerlaw_exp': (-5., 5.), 'powerlaw_ext': (0., 5.)}``.

                 - A uniform prior is used for ``powerlaw_exp`` and ``powerlaw_ext``, and a
                   log-uniform prior for ``powerlaw_max``.

                 - Only supported by ``run_multinest``.

        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.
        fit_corr : list(str), None
            List with spectrum names for which the correlation length and fractional amplitude are
            fitted (see Wang et al. 2020).

        Returns
        -------
        NoneType
            None
        """

        if not inc_phot and not inc_spec:
            raise ValueError(
                'No photometric or spectroscopic data has been selected.')

        if model == 'planck' and 'teff' not in bounds or 'radius' not in bounds:
            raise ValueError(
                'The \'bounds\' dictionary should contain \'teff\' and \'radius\'.'
            )

        self.object = read_object.ReadObject(object_name)
        self.distance = self.object.get_distance()

        if fit_corr is None:
            self.fit_corr = []
        else:
            self.fit_corr = fit_corr

        self.model = model
        self.bounds = bounds

        if self.model == 'planck':
            # Fitting blackbody radiation
            if isinstance(bounds['teff'], list) and isinstance(
                    bounds['radius'], list):
                # Update temperature and radius parameters in case of multiple blackbody components
                self.n_planck = len(bounds['teff'])

                self.modelpar = []
                self.bounds = {}

                for i, item in enumerate(bounds['teff']):
                    self.modelpar.append(f'teff_{i}')
                    self.modelpar.append(f'radius_{i}')

                    self.bounds[f'teff_{i}'] = bounds['teff'][i]
                    self.bounds[f'radius_{i}'] = bounds['radius'][i]

            else:
                # Fitting a single blackbody compoentn
                self.n_planck = 1

                self.modelpar = ['teff', 'radius']
                self.bounds = bounds

        else:
            # Fitting self-consistent atmospheric models
            if self.bounds is not None:
                readmodel = read_model.ReadModel(self.model)
                bounds_grid = readmodel.get_bounds()

                for item in bounds_grid:
                    if item not in self.bounds:
                        # Set the parameter boundaries to the grid boundaries if set to None
                        self.bounds[item] = bounds_grid[item]

            else:
                # Set all parameter boundaries to the grid boundaries
                readmodel = read_model.ReadModel(self.model, None, None)
                self.bounds = readmodel.get_bounds()

            if 'radius' not in self.bounds:
                self.bounds['radius'] = (0.8, 1.5)

            self.n_planck = 0

            self.modelpar = readmodel.get_parameters()
            self.modelpar.append('radius')

        # Select filters and spectra

        if isinstance(inc_phot, bool):
            if inc_phot:
                # Select all filters if True
                species_db = database.Database()
                objectbox = species_db.get_object(object_name)
                inc_phot = objectbox.filters

            else:
                inc_phot = []

        if isinstance(inc_spec, bool):
            if inc_spec:
                # Select all filters if True
                species_db = database.Database()
                objectbox = species_db.get_object(object_name)
                inc_spec = list(objectbox.spectrum.keys())

            else:
                inc_spec = []

        # Include photometric data

        self.objphot = []
        self.modelphot = []

        for item in inc_phot:
            if self.model == 'planck':
                # Create SyntheticPhotometry objects when fitting a Planck function
                print(f'Creating synthetic photometry: {item}...',
                      end='',
                      flush=True)
                self.modelphot.append(photometry.SyntheticPhotometry(item))

            else:
                # Or interpolate the model grid for each filter
                print(f'Interpolating {item}...', end='', flush=True)
                readmodel = read_model.ReadModel(self.model, filter_name=item)
                readmodel.interpolate_grid(wavel_resample=None,
                                           smooth=False,
                                           spec_res=None)
                self.modelphot.append(readmodel)

            print(' [DONE]')

            # Store the flux and uncertainty for each filter
            obj_phot = self.object.get_photometry(item)
            self.objphot.append(np.array([obj_phot[2], obj_phot[3]]))

        # Include spectroscopic data

        if inc_spec:
            # Select all spectra
            self.spectrum = self.object.get_spectrum()

            # Select the spectrum names that are not in inc_spec
            spec_remove = []
            for item in self.spectrum:
                if item not in inc_spec:
                    spec_remove.append(item)

            # Remove the spectra that are not included in inc_spec
            for item in spec_remove:
                del self.spectrum[item]

            self.n_corr_par = 0

            for item in self.spectrum:
                if item in self.fit_corr:
                    self.modelpar.append(f'corr_len_{item}')
                    self.modelpar.append(f'corr_amp_{item}')

                    self.bounds[f'corr_len_{item}'] = (
                        -3., 0.)  # log10(corr_len) (um)
                    self.bounds[f'corr_amp_{item}'] = (0., 1.)

                    self.n_corr_par += 2

            self.modelspec = []

            if self.model != 'planck':

                for key, value in self.spectrum.items():
                    print(f'\rInterpolating {key}...', end='', flush=True)

                    wavel_range = (0.9 * value[0][0, 0], 1.1 * value[0][-1, 0])

                    readmodel = read_model.ReadModel(self.model,
                                                     wavel_range=wavel_range)

                    readmodel.interpolate_grid(
                        wavel_resample=self.spectrum[key][0][:, 0],
                        smooth=True,
                        spec_res=self.spectrum[key][3])

                    self.modelspec.append(readmodel)

                    print(' [DONE]')

        else:
            self.spectrum = {}
            self.modelspec = None
            self.n_corr_par = 0

        for item in self.spectrum:
            if item in bounds:

                if bounds[item][0] is not None:
                    # Add the flux scaling parameter
                    self.modelpar.append(f'scaling_{item}')
                    self.bounds[f'scaling_{item}'] = (bounds[item][0][0],
                                                      bounds[item][0][1])

                if bounds[item][1] is not None:
                    # Add the error offset parameters
                    self.modelpar.append(f'error_{item}')
                    self.bounds[f'error_{item}'] = (bounds[item][1][0],
                                                    bounds[item][1][1])

                if item in self.bounds:
                    del self.bounds[item]

        if 'lognorm_radius' in self.bounds and 'lognorm_sigma' in self.bounds and \
                'lognorm_ext' in self.bounds:

            self.cross_sections, _, _ = dust_util.interp_lognorm(
                inc_phot, inc_spec, self.spectrum)

            self.modelpar.append('lognorm_radius')
            self.modelpar.append('lognorm_sigma')
            self.modelpar.append('lognorm_ext')

            self.bounds['lognorm_radius'] = (
                np.log10(self.bounds['lognorm_radius'][0]),
                np.log10(self.bounds['lognorm_radius'][1]))

        elif 'powerlaw_max' in self.bounds and 'powerlaw_exp' in self.bounds and \
                'powerlaw_ext' in self.bounds:

            self.cross_sections, _, _ = dust_util.interp_powerlaw(
                inc_phot, inc_spec, self.spectrum)

            self.modelpar.append('powerlaw_max')
            self.modelpar.append('powerlaw_exp')
            self.modelpar.append('powerlaw_ext')

            self.bounds['powerlaw_max'] = (np.log10(
                self.bounds['powerlaw_max'][0]),
                                           np.log10(
                                               self.bounds['powerlaw_max'][1]))

        else:
            self.cross_sections = None

        if 'ism_ext' in self.bounds and 'ism_red' in self.bounds:
            self.modelpar.append('ism_ext')
            self.modelpar.append('ism_red')

        print(f'Fitting {len(self.modelpar)} parameters:')

        for item in self.modelpar:
            print(f'   - {item}')

        print('Prior boundaries:')

        for key, value in self.bounds.items():
            print(f'   - {key} = {value}')
示例#18
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,
        )
示例#19
0
    def __init__(self,
                 objname,
                 filters,
                 model,
                 bounds,
                 inc_phot=True,
                 inc_spec=True):
        """
        Parameters
        ----------
        objname : str
            Object name in the database.
        filters : tuple(str, )
            Filter IDs for which the photometry is selected. All available photometry of the
            object is selected if set to None.
        model : str
            Atmospheric model.
        bounds : dict
            Parameter boundaries. Full parameter range is used if set to None or not specified.
            The radius parameter range is set to 0-5 Rjup if not specified.
        inc_phot : bool
            Include photometry data with the fit.
        inc_spec : bool
            Include spectral data with the fit.

        Returns
        -------
        NoneType
            None
        """

        self.object = read_object.ReadObject(objname)
        self.distance = self.object.get_distance()

        self.model = model
        self.bounds = bounds

        if not inc_phot and not inc_spec:
            raise ValueError('No photometric or spectral data has been selected.')

        if self.bounds is not None and 'teff' in self.bounds:
            teff_bound = self.bounds['teff']
        else:
            teff_bound = None

        if self.bounds is not None:
            readmodel = read_model.ReadModel(self.model, None, teff_bound)
            bounds_grid = readmodel.get_bounds()

            for item in bounds_grid:
                if item not in self.bounds:
                    self.bounds[item] = bounds_grid[item]

        else:
            readmodel = read_model.ReadModel(self.model, None, None)
            self.bounds = readmodel.get_bounds()

        if 'radius' not in self.bounds:
            self.bounds['radius'] = (0., 5.)

        if inc_phot:
            self.objphot = []
            self.modelphot = []
            self.synphot = []

            if not filters:
                species_db = database.Database()
                objectbox = species_db.get_object(objname, None)
                filters = objectbox.filter

            for item in filters:
                readmodel = read_model.ReadModel(self.model, item, teff_bound)
                readmodel.interpolate()
                self.modelphot.append(readmodel)

                sphot = photometry.SyntheticPhotometry(item)
                self.synphot.append(sphot)

                obj_phot = self.object.get_photometry(item)
                self.objphot.append((obj_phot[2], obj_phot[3]))

        else:
            self.objphot = None
            self.modelphot = None
            self.synphot = None

        if inc_spec:
            self.spectrum = self.object.get_spectrum()
            self.instrument = self.object.get_instrument()
            self.modelspec = read_model.ReadModel(self.model, (0.9, 2.5), teff_bound)

        else:
            self.spectrum = None
            self.instrument = None
            self.modelspec = None

        self.modelpar = readmodel.get_parameters()
        self.modelpar.append('radius')
示例#20
0
    def compare_model(
        self,
        tag: str,
        model: str,
        av_points: Optional[Union[List[float], np.array]] = None,
        fix_logg: Optional[float] = None,
        scale_spec: Optional[List[str]] = None,
        weights: bool = True,
        inc_phot: Optional[List[str]] = None,
    ) -> None:
        """
        Method for finding the best fitting spectrum from a grid of atmospheric model spectra by
        evaluating the goodness-of-fit statistic from Cushing et al. (2008). Currently, this method
        only supports model grids with only :math:`T_\\mathrm{eff}` and :math:`\\log(g)` as free
        parameters (e.g. BT-Settl). Please create an issue on Github if support for models with
        more than two parameters is required.

        Parameters
        ----------
        tag : str
            Database tag where for each spectrum from the spectral library the best-fit parameters
            will be stored. So when testing a range of values for ``av_ext`` and ``rad_vel``, only
            the parameters that minimize the goodness-of-fit statistic will be stored.
        model : str
            Name of the atmospheric model grid with synthetic spectra.
        av_points : list(float), np.array, None
            List of :math:`A_V` extinction values for which the goodness-of-fit statistic will be
            tested. The extinction is calculated with the relation from Cardelli et al. (1989).
        fix_logg : float, None
            Fix the value of :math:`\\log(g)`, for example if estimated from gravity-sensitive
            spectral features. Typically, :math:`\\log(g)` can not be accurately determined when
            comparing the spectra over a broad wavelength range.
        scale_spec : list(str), None
            List with names of observed spectra to which a flux scaling is applied to best match
            the spectral templates.
        weights : bool
            Apply a weighting based on the widths of the wavelengths bins.
        inc_phot : list(str), None
            Filter names of the photometry to include in the comparison. Photometry points are
            weighted by the FWHM of the filter profile. No photometric fluxes will be used if the
            argument is set to ``None``.

        Returns
        -------
        NoneType
            None
        """

        w_i = {}

        for spec_item in self.spec_name:
            obj_wavel = self.object.get_spectrum()[spec_item][0][:, 0]

            diff = (np.diff(obj_wavel)[1:] + np.diff(obj_wavel)[:-1]) / 2.0
            diff = np.insert(diff, 0, diff[0])
            diff = np.append(diff, diff[-1])

            if weights:
                w_i[spec_item] = diff
            else:
                w_i[spec_item] = np.ones(obj_wavel.shape[0])

        if inc_phot is None:
            inc_phot = []

        if scale_spec is None:
            scale_spec = []

        phot_wavel = {}

        for phot_item in inc_phot:
            read_filt = read_filter.ReadFilter(phot_item)
            w_i[phot_item] = read_filt.filter_fwhm()
            phot_wavel[phot_item] = read_filt.mean_wavelength()

        if av_points is None:
            av_points = np.array([0.0])

        elif isinstance(av_points, list):
            av_points = np.array(av_points)

        readmodel = read_model.ReadModel(model)

        model_param = readmodel.get_parameters()
        grid_points = readmodel.get_points()

        coord_points = []
        for key, value in grid_points.items():
            if key == "logg" and fix_logg is not None:
                if fix_logg in value:
                    coord_points.append(np.array([fix_logg]))

                else:
                    raise ValueError(
                        f"The argument of 'fix_logg' ({fix_logg}) is not found "
                        f"in the parameter grid of the model spectra. The following "
                        f"values of log(g) are available: {value}")

            else:
                coord_points.append(value)

        if av_points is not None:
            model_param.append("ism_ext")
            coord_points.append(av_points)

        grid_shape = []

        for item in coord_points:
            grid_shape.append(len(item))

        fit_stat = np.zeros(grid_shape)
        flux_scaling = np.zeros(grid_shape)

        if len(scale_spec) == 0:
            extra_scaling = None

        else:
            grid_shape.append(len(scale_spec))
            extra_scaling = np.zeros(grid_shape)

        count = 1

        if len(coord_points) == 3:
            n_iter = len(coord_points[0]) * len(coord_points[1]) * len(
                coord_points[2])

            for i, item_i in enumerate(coord_points[0]):
                for j, item_j in enumerate(coord_points[1]):
                    for k, item_k in enumerate(coord_points[2]):
                        print(
                            f"\rProcessing model spectrum {count}/{n_iter}...",
                            end="")

                        model_spec = {}
                        model_phot = {}

                        for spec_item in self.spec_name:
                            obj_spec = self.object.get_spectrum()[spec_item][0]
                            obj_res = self.object.get_spectrum()[spec_item][3]

                            param_dict = {
                                model_param[0]: item_i,
                                model_param[1]: item_j,
                                model_param[2]: item_k,
                            }

                            wavel_range = (0.9 * obj_spec[0, 0],
                                           1.1 * obj_spec[-1, 0])
                            readmodel = read_model.ReadModel(
                                model, wavel_range=wavel_range)

                            model_box = readmodel.get_data(
                                param_dict,
                                spec_res=obj_res,
                                wavel_resample=obj_spec[:, 0],
                            )

                            model_spec[spec_item] = model_box.flux

                        for phot_item in inc_phot:
                            readmodel = read_model.ReadModel(
                                model, filter_name=phot_item)

                            model_phot[phot_item] = readmodel.get_flux(
                                param_dict)[0]

                        def g_fit(x, scaling):
                            g_stat = 0.0

                            for spec_item in self.spec_name:
                                obs_spec = self.object.get_spectrum(
                                )[spec_item][0]

                                if spec_item in scale_spec:
                                    spec_idx = scale_spec.index(spec_item)

                                    c_numer = (w_i[spec_item] *
                                               obs_spec[:, 1] *
                                               model_spec[spec_item] /
                                               obs_spec[:, 2]**2)

                                    c_denom = (w_i[spec_item] *
                                               model_spec[spec_item]**2 /
                                               obs_spec[:, 2]**2)

                                    extra_scaling[i, j, k, spec_idx] = np.sum(
                                        c_numer) / np.sum(c_denom)

                                    g_stat += np.sum(
                                        w_i[spec_item] *
                                        (obs_spec[:, 1] -
                                         extra_scaling[i, j, k, spec_idx] *
                                         model_spec[spec_item])**2 /
                                        obs_spec[:, 2]**2)

                                else:
                                    g_stat += np.sum(
                                        w_i[spec_item] *
                                        (obs_spec[:, 1] -
                                         scaling * model_spec[spec_item])**2 /
                                        obs_spec[:, 2]**2)

                            for phot_item in inc_phot:
                                obs_phot = self.object.get_photometry(
                                    phot_item)

                                g_stat += (
                                    w_i[phot_item] *
                                    (obs_phot[2] -
                                     scaling * model_phot[phot_item])**2 /
                                    obs_phot[3]**2)

                            return g_stat

                        popt, _ = curve_fit(g_fit, xdata=[0.0], ydata=[0.0])
                        scaling = popt[0]

                        flux_scaling[i, j, k] = scaling
                        fit_stat[i, j, k] = g_fit(0.0, scaling)

                        count += 1

        print(" [DONE]")

        species_db = database.Database()

        species_db.add_comparison(
            tag=tag,
            goodness_of_fit=fit_stat,
            flux_scaling=flux_scaling,
            model_param=model_param,
            coord_points=coord_points,
            object_name=self.object_name,
            spec_name=self.spec_name,
            model=model,
            scale_spec=scale_spec,
            extra_scaling=extra_scaling,
        )
示例#21
0
    def get_color_magnitude(self,
                            age: float,
                            masses: np.ndarray,
                            model: str,
                            filters_color: Tuple[str, str],
                            filter_mag: str) -> 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 (Mjup) 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.

        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])

        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])

        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

            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

                    elif model_param[item_bounds] >= model1.get_bounds()[item_bounds][1]:
                        mag1[i] = np.nan
                        mag2[i] = np.nan

                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 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='model',
                              filters_color=filters_color,
                              filter_mag=filter_mag,
                              color=mag1-mag2,
                              magnitude=abs_mag,
                              sptype=masses,
                              names=None)