Пример #1
0
    def run_mcmc(self,
                 nwalkers: int,
                 nsteps: int,
                 guess: Dict[str, float],
                 tag: str) -> None:
        """
        Function to run the MCMC sampler.

        Parameters
        ----------
        nwalkers : int
            Number of walkers.
        nsteps : int
            Number of steps per walker.
        guess : dict
            Guess of the scaling parameter.
        tag : str
            Database tag where the MCMC samples are stored.

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

        print('Running MCMC...')

        ndim = 1

        initial = np.zeros((nwalkers, ndim))
        initial[:, 0] = guess['scaling'] + np.random.normal(0, 1e-1*guess['scaling'], nwalkers)

        if ndim > 1:
            for i in range(1, ndim):
                initial[:, i] = 1. + np.random.normal(0, 0.1, nwalkers)
                self.modelpar.append('scaling'+str(i))
                self.bounds['scaling'+str(i)] = (0., 1e2)

        with Pool(processes=cpu_count()):
            ens_sampler = emcee.EnsembleSampler(nwalkers,
                                                ndim,
                                                lnprob,
                                                args=([self.bounds,
                                                       self.modelpar,
                                                       self.objphot,
                                                       self.specphot]))

            ens_sampler.run_mcmc(initial, nsteps, progress=True)

        species_db = database.Database()

        species_db.add_samples(sampler='emcee',
                               samples=ens_sampler.chain,
                               ln_prob=ens_sampler.lnprobability,
                               mean_accept=np.mean(ens_sampler.acceptance_fraction),
                               spectrum=('calibration', self.spectrum),
                               tag=tag,
                               modelpar=self.modelpar,
                               distance=None,
                               spec_labels=None)
Пример #2
0
def check_dust_database() -> str:
    """
    Function to check if the dust data is present in the database and add the data if needed.

    Returns
    -------
    str
        The database path from the configuration file.
    """

    config_file = os.path.join(os.getcwd(), 'species_config.ini')

    config = configparser.ConfigParser()
    config.read_file(open(config_file))

    database_path = config['species']['database']

    h5_file = h5py.File(database_path, 'r')

    if 'dust' not in h5_file:
        h5_file.close()
        species_db = database.Database()
        species_db.add_dust()
        h5_file = h5py.File(database_path, 'r')

    h5_file.close()

    return database_path
Пример #3
0
    def get_filter(self):
        """
        Returns
        -------
        numpy.ndarray
            Filter data.
        """

        h5_file = h5py.File(self.database, 'r')

        try:
            h5_file['filters/' + self.filter_name]

        except KeyError:
            h5_file.close()
            species_db = database.Database()
            species_db.add_filter(self.filter_name)
            h5_file = h5py.File(self.database, 'r')

        data = h5_file['filters/' + self.filter_name]
        data = np.asarray(data)

        h5_file.close()

        return data
Пример #4
0
    def __init__(self, filter_name: str) -> None:
        """
        Parameters
        ----------
        filter_name : str
            Filter name as stored in the database. Filter names from
            the SVO Filter Profile Service will be automatically
            downloaded, stored in the database, and read from the
            database.

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

        self.filter_name = filter_name

        config_file = os.path.join(os.getcwd(), "species_config.ini")

        config = configparser.ConfigParser()
        config.read(config_file)

        self.database = config["species"]["database"]

        h5_file = h5py.File(self.database, "r")

        if "filters" not in h5_file or self.filter_name not in h5_file[
                "filters"]:
            h5_file.close()
            species_db = database.Database()
            species_db.add_filter(self.filter_name)
            h5_file = h5py.File(self.database, "r")

        h5_file.close()
Пример #5
0
    def __init__(self, filter_name: str) -> None:
        """
        Parameters
        ----------
        filter_name : str
            Filter name as stored in the database. Filter names from the SVO Filter Profile Service
            will be automatically downloaded, stored in the database, and read from the database.

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

        self.filter_name = filter_name

        config_file = os.path.join(os.getcwd(), 'species_config.ini')

        config = configparser.ConfigParser()
        config.read_file(open(config_file))

        self.database = config['species']['database']

        h5_file = h5py.File(self.database, 'r')

        if 'filters' not in h5_file or self.filter_name not in h5_file[
                'filters']:
            h5_file.close()
            species_db = database.Database()
            species_db.add_filter(self.filter_name)
            h5_file = h5py.File(self.database, 'r')

        h5_file.close()
Пример #6
0
    def open_database(self):
        """
        Returns
        -------
        h5py._hl.files.File
            Database.
        """

        config_file = os.path.join(os.getcwd(), 'species_config.ini')

        config = configparser.ConfigParser()
        config.read_file(open(config_file))

        database_path = config['species']['database']

        h5_file = h5py.File(database_path, 'r')

        try:
            h5_file['models/' + self.model]

        except KeyError:
            h5_file.close()
            species_db = database.Database()
            species_db.add_model(self.model, self.wavelength, self.teff)
            h5_file = h5py.File(database_path, 'r')

        return h5_file
Пример #7
0
def get_parallax():
    species_db = database.Database()
    species_db.add_photometry('vlm-plx')

    with h5py.File(species_db.database, 'a') as hdf_file:
        name = np.asarray(hdf_file['photometry/vlm-plx/name'])
        ra_coord = np.asarray(hdf_file['photometry/vlm-plx/ra'])
        dec_coord = np.asarray(hdf_file['photometry/vlm-plx/dec'])
        distance = np.asarray(hdf_file['photometry/vlm-plx/distance'])
        distance_error = np.asarray(
            hdf_file['photometry/vlm-plx/distance_error'])

        simbad_id = []

        print('Querying SIMBAD...', end='', flush=True)

        for i, item in enumerate(name):
            target_coord = SkyCoord(ra_coord[i],
                                    dec_coord[i],
                                    unit=(u.deg, u.deg),
                                    frame='icrs')

            result_table = Simbad.query_region(target_coord, radius='0d0m2s')

            if result_table is None:
                result_table = Simbad.query_region(target_coord,
                                                   radius='0d0m5s')

            if result_table is None:
                result_table = Simbad.query_region(target_coord,
                                                   radius='0d0m20s')

            if result_table is None:
                result_table = Simbad.query_region(target_coord,
                                                   radius='0d1m0s')

            if item == 'HIP38939B':
                simbad_id.append(get_simbad('HIP38939').decode('utf-8'))
            else:
                simbad_id.append(result_table['MAIN_ID'][0].decode('utf-8'))

        print(' [DONE]')

        simbad_id = np.asarray(simbad_id)

        dtype = h5py.special_dtype(vlen=str)

        dset = hdf_file.create_dataset('photometry/vlm-plx/simbad',
                                       (np.size(simbad_id), ),
                                       dtype=dtype)

        dset[...] = simbad_id

        np.savetxt(
            'parallax.dat',
            np.column_stack([name, simbad_id, distance, distance_error]),
            header='VLM-PLX name - SIMBAD name - Distance (pc) - Error (pc)',
            fmt='%35s, %35s, %8.2f, %8.2f')
Пример #8
0
    def __init__(
        self,
        object_name: str,
        filters: Optional[List[str]],
        spectrum: str,
        bounds: Dict[str, Tuple[float, float]],
    ) -> None:
        """
        Parameters
        ----------
        object_name : str
            Object name in the database.
        filters : list(str)
            Filter names for which the photometry is selected. All available photometry of the
            object is selected if set to ``None``.
        spectrum : str
            Calibration spectrum as labelled in the database. The calibration spectrum can be
            stored in the database with :func:`~species.data.database.Database.add_calibration`.
        bounds : dict
            Boundaries of the scaling parameter, as ``{'scaling':(min, max)}``.

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

        self.object = read_object.ReadObject(object_name)

        self.spectrum = spectrum
        self.bounds = bounds

        self.objphot = []
        self.specphot = []

        if filters is None:
            species_db = database.Database()

            objectbox = species_db.get_object(object_name,
                                              inc_phot=True,
                                              inc_spec=False)
            filters = objectbox.filters

        for item in filters:
            readcalib = read_calibration.ReadCalibration(self.spectrum, item)
            calibspec = readcalib.get_spectrum()

            synphot = photometry.SyntheticPhotometry(item)
            spec_phot = synphot.spectrum_to_flux(calibspec.wavelength,
                                                 calibspec.flux)
            self.specphot.append(spec_phot[0])

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

        self.modelpar = ["scaling"]
Пример #9
0
    def __init__(self, objname, filters, spectrum, bounds):
        """
        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.
        spectrum : str
            Calibration spectrum.
        bounds : dict
            Boundaries of the scaling parameter, as {'scaling':(min, max)}.

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

        self.object = read_object.ReadObject(objname)

        self.spectrum = spectrum
        self.bounds = bounds

        self.objphot = []
        self.specphot = []

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

        for item in filters:
            readcalib = read_calibration.ReadCalibration(self.spectrum, item)
            calibspec = readcalib.get_spectrum()

            synphot = photometry.SyntheticPhotometry(item)
            spec_phot = synphot.spectrum_to_photometry(calibspec.wavelength,
                                                       calibspec.flux)
            self.specphot.append(spec_phot)

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

        self.modelpar = ['scaling']
Пример #10
0
    def zero_point(self) -> np.float64:
        """
        Internal function for calculating the zero point
        of the provided ``filter_name``.

        Returns
        -------
        float
            Zero-point flux (W m-2 um-1).
        """

        if self.wavel_range is None:
            transmission = read_filter.ReadFilter(self.filter_name)
            self.wavel_range = transmission.wavelength_range()

        h5_file = h5py.File(self.database, "r")

        try:
            h5_file["spectra/calibration/vega"]

        except KeyError:
            h5_file.close()
            species_db = database.Database()
            species_db.add_spectra("vega")
            h5_file = h5py.File(self.database, "r")

        readcalib = read_calibration.ReadCalibration("vega", None)
        calibbox = readcalib.get_spectrum()

        wavelength = calibbox.wavelength
        flux = calibbox.flux

        wavelength_crop = wavelength[(wavelength > self.wavel_range[0])
                                     & (wavelength < self.wavel_range[1])]

        flux_crop = flux[(wavelength > self.wavel_range[0])
                         & (wavelength < self.wavel_range[1])]

        h5_file.close()

        return self.spectrum_to_flux(wavelength_crop, flux_crop)[0]
Пример #11
0
def check_dust_database() -> str:
    """
    Function to check if the dust data is present in the database and
    add the data if needed.

    Returns
    -------
    str
        The database path from the configuration file.
    """

    config_file = os.path.join(os.getcwd(), "species_config.ini")

    config = configparser.ConfigParser()
    config.read(config_file)

    database_path = config["species"]["database"]

    if "dust" not in h5py.File(database_path, "r"):
        species_db = database.Database()
        species_db.add_dust()

    return database_path
Пример #12
0
    def zero_point(self):
        """
        Returns
        -------
        tuple(float, float)
        """

        if self.wl_range is None:
            transmission = read_filter.ReadFilter(self.filter_name)
            self.wl_range = transmission.wavelength_range()

        h5_file = h5py.File(self.database, 'r')

        try:
            h5_file['spectra/calibration/vega']

        except KeyError:
            h5_file.close()
            species_db = database.Database()
            species_db.add_spectrum('vega')
            h5_file = h5py.File(self.database, 'r')

        readcalib = read_calibration.ReadCalibration('vega', None)
        calibbox = readcalib.get_spectrum()

        wavelength = calibbox.wavelength
        flux = calibbox.flux

        wavelength_crop = wavelength[(wavelength > self.wl_range[0])
                                     & (wavelength < self.wl_range[1])]

        flux_crop = flux[(wavelength > self.wl_range[0])
                         & (wavelength < self.wl_range[1])]

        h5_file.close()

        return self.spectrum_to_photometry(wavelength_crop, flux_crop)
Пример #13
0
    def run_multinest(
            self,
            tag: str,
            n_live_points: int = 1000,
            output: str = 'multinest/',
            prior: Optional[Dict[str, Tuple[float, float]]] = None) -> None:
        """
        Function to run the ``PyMultiNest`` wrapper of the ``MultiNest`` sampler. While
        ``PyMultiNest`` can be installed with ``pip`` from the PyPI repository, ``MultiNest``
        has to to be build manually. See the ``PyMultiNest`` documentation for details:
        http://johannesbuchner.github.io/PyMultiNest/install.html. Note that the library path
        of ``MultiNest`` should be set to the environmental variable ``LD_LIBRARY_PATH`` on a
        Linux machine and ``DYLD_LIBRARY_PATH`` on a Mac. Alternatively, the variable can be
        set before importing the ``species`` package, for example:

        .. code-block:: python

            >>> import os
            >>> os.environ['DYLD_LIBRARY_PATH'] = '/path/to/MultiNest/lib'
            >>> import species

        Parameters
        ----------
        tag : str
            Database tag where the samples will be stored.
        n_live_points : int
            Number of live points.
        output : str
            Path that is used for the output files from MultiNest.
        prior : dict(str, tuple(float, float)), None
            Dictionary with Gaussian priors for one or multiple parameters. The prior can be set
            for any of the atmosphere or calibration parameters, e.g.
            ``prior={'teff': (1200., 100.)}``. Additionally, a prior can be set for the mass, e.g.
            ``prior={'mass': (13., 3.)}`` for an expected mass of 13 Mjup with an uncertainty of
            3 Mjup. The parameter is not used if set to ``None``.

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

        print('Running nested sampling...')

        # Create the output folder if required

        if not os.path.exists(output):
            os.mkdir(output)

        # Create a dictionary with the cube indices of the parameters

        cube_index = {}
        for i, item in enumerate(self.modelpar):
            cube_index[item] = i

        @typechecked
        def lnprior_multinest(cube, n_dim: int, n_param: int) -> None:
            """
            Function to transform the unit cube into the parameter cube. It is not clear how to
            pass additional arguments to the function, therefore it is placed here and not merged
            with :func:`~species.analysis.fit_model.FitModel.run_mcmc`.

            Parameters
            ----------
            cube : pymultinest.run.LP_c_double
                Unit cube.
            n_dim : int
                Number of dimensions.
            n_param : int
                Number of parameters.

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

            for item in cube_index:
                # Uniform priors for all parameters
                cube[cube_index[item]] = self.bounds[item][0] + \
                    (self.bounds[item][1]-self.bounds[item][0])*cube[cube_index[item]]

        @typechecked
        def lnlike_multinest(cube, n_dim: int, n_param: int) -> np.float64:
            """
            Function for the logarithm of the likelihood, computed from the parameter cube.

            Parameters
            ----------
            cube : pymultinest.run.LP_c_double
                Unit cube.
            n_dim : int
                Number of dimensions.
            n_param : int
                Number of parameters.

            Returns
            -------
            float
                Log likelihood.
            """

            param_dict = {}
            spec_scaling = {}
            err_offset = {}
            corr_len = {}
            corr_amp = {}
            dust_param = {}

            for item in self.bounds:
                if item[:8] == 'scaling_' and item[8:] in self.spectrum:
                    spec_scaling[item[8:]] = cube[cube_index[item]]

                elif item[:6] == 'error_' and item[6:] in self.spectrum:
                    err_offset[item[6:]] = cube[cube_index[item]]  # log10(um)

                elif item[:9] == 'corr_len_' and item[9:] in self.spectrum:
                    corr_len[item[9:]] = 10.**cube[cube_index[item]]  # (um)

                elif item[:9] == 'corr_amp_' and item[9:] in self.spectrum:
                    corr_amp[item[9:]] = cube[cube_index[item]]

                elif item[:8] == 'lognorm_':
                    dust_param[item] = cube[cube_index[item]]

                elif item[:9] == 'powerlaw_':
                    dust_param[item] = cube[cube_index[item]]

                elif item[:4] == 'ism_':
                    dust_param[item] = cube[cube_index[item]]

                else:
                    param_dict[item] = cube[cube_index[item]]

            if self.model == 'planck':
                param_dict['distance'] = self.distance[0]

            else:
                flux_scaling = (param_dict['radius']*constants.R_JUP)**2 / \
                               (self.distance[0]*constants.PARSEC)**2

                # The scaling is applied manually because of the interpolation
                del param_dict['radius']

            for item in self.spectrum:
                if item not in spec_scaling:
                    spec_scaling[item] = 1.

                if item not in err_offset:
                    err_offset[item] = None

            ln_like = 0.

            if self.model == 'planck' and self.n_planck > 1:
                for i in range(self.n_planck - 1):
                    if param_dict[f'teff_{i+1}'] > param_dict[f'teff_{i}']:
                        return -np.inf

                    if param_dict[f'radius_{i}'] > param_dict[f'radius_{i+1}']:
                        return -np.inf

            if prior is not None:
                for key, value in prior.items():
                    if key == 'mass':
                        mass = read_util.get_mass(cube[cube_index['logg']],
                                                  cube[cube_index['radius']])

                        ln_like += -0.5 * (mass - value[0])**2 / value[1]**2

                    else:
                        ln_like += -0.5 * (cube[cube_index[key]] -
                                           value[0])**2 / value[1]**2

            if 'lognorm_ext' in dust_param:
                cross_tmp = self.cross_sections['Generic/Bessell.V'](
                    dust_param['lognorm_sigma'],
                    10.**dust_param['lognorm_radius'])[0]

                n_grains = dust_param[
                    'lognorm_ext'] / cross_tmp / 2.5 / np.log10(np.exp(1.))

            elif 'powerlaw_ext' in dust_param:
                cross_tmp = self.cross_sections['Generic/Bessell.V'](
                    dust_param['powerlaw_exp'],
                    10.**dust_param['powerlaw_max'])

                n_grains = dust_param[
                    'powerlaw_ext'] / cross_tmp / 2.5 / np.log10(np.exp(1.))

            for i, obj_item in enumerate(self.objphot):
                if self.model == 'planck':
                    readplanck = read_planck.ReadPlanck(
                        filter_name=self.modelphot[i].filter_name)
                    phot_flux = readplanck.get_flux(
                        param_dict, synphot=self.modelphot[i])[0]

                else:
                    phot_flux = self.modelphot[i].spectrum_interp(
                        list(param_dict.values()))[0][0]
                    phot_flux *= flux_scaling

                if 'lognorm_ext' in dust_param:
                    cross_tmp = self.cross_sections[
                        self.modelphot[i].filter_name](
                            dust_param['lognorm_sigma'],
                            10.**dust_param['lognorm_radius'])[0]

                    phot_flux *= np.exp(-cross_tmp * n_grains)

                elif 'powerlaw_ext' in dust_param:
                    cross_tmp = self.cross_sections[
                        self.modelphot[i].filter_name](
                            dust_param['powerlaw_exp'],
                            10.**dust_param['powerlaw_max'])[0]

                    phot_flux *= np.exp(-cross_tmp * n_grains)

                elif 'ism_ext' in dust_param:
                    read_filt = read_filter.ReadFilter(
                        self.modelphot[i].filter_name)
                    filt_wavel = np.array([read_filt.mean_wavelength()])

                    ext_filt = dust_util.ism_extinction(
                        dust_param['ism_ext'], dust_param['ism_red'],
                        filt_wavel)

                    phot_flux *= 10.**(-0.4 * ext_filt[0])

                if obj_item.ndim == 1:
                    ln_like += -0.5 * (obj_item[0] -
                                       phot_flux)**2 / obj_item[1]**2

                else:
                    for j in range(obj_item.shape[1]):
                        ln_like += -0.5 * (obj_item[0, j] -
                                           phot_flux)**2 / obj_item[1, j]**2

            for i, item in enumerate(self.spectrum.keys()):
                data_flux = spec_scaling[item] * self.spectrum[item][0][:, 1]

                if err_offset[item] is None:
                    data_var = self.spectrum[item][0][:, 2]**2
                else:
                    data_var = (self.spectrum[item][0][:, 2] +
                                10.**err_offset[item])**2

                if self.spectrum[item][2] is not None:
                    if err_offset[item] is None:
                        data_cov_inv = self.spectrum[item][2]

                    else:
                        # Ratio of the inflated and original uncertainties
                        sigma_ratio = np.sqrt(
                            data_var) / self.spectrum[item][0][:, 2]
                        sigma_j, sigma_i = np.meshgrid(sigma_ratio,
                                                       sigma_ratio)

                        # Calculate the inversion of the infalted covariances
                        data_cov_inv = np.linalg.inv(self.spectrum[item][1] *
                                                     sigma_i * sigma_j)

                if self.model == 'planck':
                    readplanck = read_planck.ReadPlanck(
                        (0.9 * self.spectrum[item][0][0, 0],
                         1.1 * self.spectrum[item][0][-1, 0]))

                    model_box = readplanck.get_spectrum(param_dict,
                                                        1000.,
                                                        smooth=True)

                    model_flux = spectres.spectres(
                        self.spectrum[item][0][:, 0], model_box.wavelength,
                        model_box.flux)

                else:
                    model_flux = self.modelspec[i].spectrum_interp(
                        list(param_dict.values()))[0, :]
                    model_flux *= flux_scaling

                if 'lognorm_ext' in dust_param:
                    for j, cross_item in enumerate(self.cross_sections[item]):
                        cross_tmp = cross_item(
                            dust_param['lognorm_sigma'],
                            10.**dust_param['lognorm_radius'])[0]

                        model_flux[j] *= np.exp(-cross_tmp * n_grains)

                elif 'powerlaw_ext' in dust_param:
                    for j, cross_item in enumerate(self.cross_sections[item]):
                        cross_tmp = cross_item(
                            dust_param['powerlaw_exp'],
                            10.**dust_param['powerlaw_max'])[0]

                        model_flux[j] *= np.exp(-cross_tmp * n_grains)

                elif 'ism_ext' in dust_param:
                    ext_filt = dust_util.ism_extinction(
                        dust_param['ism_ext'], dust_param['ism_red'],
                        self.spectrum[item][0][:, 0])

                    model_flux *= 10.**(-0.4 * ext_filt)

                if self.spectrum[item][2] is not None:
                    # Use the inverted covariance matrix
                    dot_tmp = np.dot(
                        data_flux - model_flux,
                        np.dot(data_cov_inv, data_flux - model_flux))

                    ln_like += -0.5 * dot_tmp - 0.5 * np.nansum(
                        np.log(2. * np.pi * data_var))

                else:
                    if item in self.fit_corr:
                        # Covariance model (Wang et al. 2020)
                        wavel = self.spectrum[item][0][:, 0]  # (um)
                        wavel_j, wavel_i = np.meshgrid(wavel, wavel)

                        error = np.sqrt(data_var)  # (W m-2 um-1)
                        error_j, error_i = np.meshgrid(error, error)

                        cov_matrix = corr_amp[item]**2 * error_i * error_j * \
                            np.exp(-(wavel_i-wavel_j)**2 / (2.*corr_len[item]**2)) + \
                            (1.-corr_amp[item]**2) * np.eye(wavel.shape[0])*error_i**2

                        dot_tmp = np.dot(
                            data_flux - model_flux,
                            np.dot(np.linalg.inv(cov_matrix),
                                   data_flux - model_flux))

                        ln_like += -0.5 * dot_tmp - 0.5 * np.nansum(
                            np.log(2. * np.pi * data_var))

                    else:
                        # Calculate the chi-square without a covariance matrix
                        ln_like += np.nansum(
                            -0.5 * (data_flux - model_flux)**2 / data_var -
                            0.5 * np.log(2. * np.pi * data_var))

            return ln_like

        pymultinest.run(lnlike_multinest,
                        lnprior_multinest,
                        len(self.modelpar),
                        outputfiles_basename=output,
                        resume=False,
                        n_live_points=n_live_points)

        # Create the Analyzer object
        analyzer = pymultinest.analyse.Analyzer(len(self.modelpar),
                                                outputfiles_basename=output)

        # Get a dictionary with the ln(Z) and its errors, the individual modes and their parameters
        # quantiles of the parameter posteriors
        stats = analyzer.get_stats()

        # Nested sampling global log-evidence
        ln_z = stats['nested sampling global log-evidence']
        ln_z_error = stats['nested sampling global log-evidence error']
        print(
            f'Nested sampling global log-evidence: {ln_z:.2f} +/- {ln_z_error:.2f}'
        )

        # Nested sampling global log-evidence
        ln_z = stats['nested importance sampling global log-evidence']
        ln_z_error = stats[
            'nested importance sampling global log-evidence error']
        print(
            f'Nested importance sampling global log-evidence: {ln_z:.2f} +/- {ln_z_error:.2f}'
        )

        # Get the best-fit (highest likelihood) point
        print('Sample with the highest likelihood:')
        best_params = analyzer.get_best_fit()

        max_lnlike = best_params['log_likelihood']
        print(f'   - Log-likelihood = {max_lnlike:.2f}')

        for i, item in enumerate(best_params['parameters']):
            print(f'   - {self.modelpar[i]} = {item:.2f}')

        # Get the posterior samples
        samples = analyzer.get_equal_weighted_posterior()

        spec_labels = []
        for item in self.spectrum:
            if f'scaling_{item}' in self.bounds:
                spec_labels.append(f'scaling_{item}')

        species_db = database.Database()

        species_db.add_samples(sampler='multinest',
                               samples=samples[:, :-1],
                               ln_prob=samples[:, -1],
                               mean_accept=None,
                               spectrum=('model', self.model),
                               tag=tag,
                               modelpar=self.modelpar,
                               distance=self.distance[0],
                               spec_labels=spec_labels)
Пример #14
0
    def get_model(self,
                  model_param: Dict[str, float],
                  spec_res: Optional[float] = None,
                  wavel_resample: Optional[np.ndarray] = None,
                  magnitude: bool = False,
                  smooth: bool = False) -> box.ModelBox:
        """
        Function for extracting a model spectrum by linearly interpolating the model grid.

        Parameters
        ----------
        model_param : dict
            Dictionary with the model parameters and values. The values should be within the
            boundaries of the grid. The grid boundaries of the spectra in the database can be
            obtained with :func:`~species.read.read_model.ReadModel.get_bounds()`.
        spec_res : float, None
            Spectral resolution that is used for smoothing the spectrum with a Gaussian kernel
            when ``smooth=True`` and/or resampling the spectrum when ``wavel_range`` of
            ``FitModel`` is not ``None``. The original wavelength points are used if both
            ``spec_res`` and ``wavel_resample`` are set to ``None``, or if ``smooth`` is set to
            ``True``.
        wavel_resample : np.ndarray, None
            Wavelength points (um) to which the spectrum is resampled. In that case, ``spec_res``
            can still be used for smoothing the spectrum with a Gaussian kernel.
        magnitude : bool
            Normalize the spectrum with a flux calibrated spectrum of Vega and return the magnitude
            instead of flux density.
        smooth : bool
            If ``True``, the spectrum is smoothed with a Gaussian kernel to the spectral resolution
            of ``spec_res``. This requires either a uniform spectral resolution of the input
            spectra (fast) or a uniform wavelength spacing of the input spectra (slow).

        Returns
        -------
        species.core.box.ModelBox
            Box with the model spectrum.
        """

        if smooth and spec_res is None:
            warnings.warn('The \'spec_res\' argument is required for smoothing the spectrum when '
                          '\'smooth\' is set to True.')

        grid_bounds = self.get_bounds()

        extra_param = ['radius', 'distance', 'mass', 'luminosity', 'lognorm_radius',
                       'lognorm_sigma', 'lognorm_ext', 'ism_ext', 'ism_red', 'powerlaw_max',
                       'powerlaw_exp', 'powerlaw_ext']

        for key in self.get_parameters():
            if key not in model_param.keys():
                raise ValueError(f'The \'{key}\' parameter is required by \'{self.model}\'. '
                                 f'The mandatory parameters are {self.get_parameters()}.')

            if model_param[key] < grid_bounds[key][0]:
                raise ValueError(f'The input value of \'{key}\' is smaller than the lower '
                                 f'boundary of the model grid ({model_param[key]} < '
                                 f'{grid_bounds[key][0]}).')

            if model_param[key] > grid_bounds[key][1]:
                raise ValueError(f'The input value of \'{key}\' is larger than the upper '
                                 f'boundary of the model grid ({model_param[key]} > '
                                 f'{grid_bounds[key][1]}).')

        for key in model_param.keys():
            if key not in self.get_parameters() and key not in extra_param:
                warnings.warn(f'The \'{key}\' parameter is not required by \'{self.model}\' so '
                              f'the parameter will be ignored. The mandatory parameters are '
                              f'{self.get_parameters()}.')

        if 'mass' in model_param and 'radius' not in model_param:
            mass = 1e3 * model_param['mass'] * constants.M_JUP  # (g)
            radius = math.sqrt(1e3 * constants.GRAVITY * mass / (10.**model_param['logg']))  # (cm)
            model_param['radius'] = 1e-2 * radius / constants.R_JUP  # (Rjup)

        if self.spectrum_interp is None:
            self.interpolate_model()

        if self.wavel_range is None:
            wl_points = self.get_wavelengths()
            self.wavel_range = (wl_points[0], wl_points[-1])

        parameters = []

        if 'teff' in model_param:
            parameters.append(model_param['teff'])

        if 'logg' in model_param:
            parameters.append(model_param['logg'])

        if 'feh' in model_param:
            parameters.append(model_param['feh'])

        if 'co' in model_param:
            parameters.append(model_param['co'])

        if 'fsed' in model_param:
            parameters.append(model_param['fsed'])

        flux = self.spectrum_interp(parameters)[0]

        if 'radius' in model_param:
            model_param['mass'] = read_util.get_mass(model_param['logg'], model_param['radius'])

            if 'distance' in model_param:
                scaling = (model_param['radius']*constants.R_JUP)**2 / \
                          (model_param['distance']*constants.PARSEC)**2

                flux *= scaling

        if smooth:
            flux = read_util.smooth_spectrum(wavelength=self.wl_points,
                                             flux=flux,
                                             spec_res=spec_res)

        if wavel_resample is not None:
            flux = spectres.spectres(wavel_resample,
                                     self.wl_points,
                                     flux,
                                     spec_errs=None,
                                     fill=np.nan,
                                     verbose=True)

        elif spec_res is not None and not smooth:
            index = np.where(np.isnan(flux))[0]

            if index.size > 0:
                raise ValueError('Flux values should not contains NaNs. Please make sure that '
                                 'the parameter values and the wavelength range are within '
                                 'the grid boundaries as stored in the database.')

            wavel_resample = read_util.create_wavelengths(
                (self.wl_points[0], self.wl_points[-1]), spec_res)

            indices = np.where((wavel_resample > self.wl_points[0]) &
                               (wavel_resample < self.wl_points[-2]))[0]

            wavel_resample = wavel_resample[indices]

            flux = spectres.spectres(wavel_resample,
                                     self.wl_points,
                                     flux,
                                     spec_errs=None,
                                     fill=np.nan,
                                     verbose=True)

        if magnitude:
            quantity = 'magnitude'

            with h5py.File(self.database, 'r') as h5_file:
                try:
                    h5_file['spectra/calibration/vega']

                except KeyError:
                    h5_file.close()
                    species_db = database.Database()
                    species_db.add_spectrum('vega')
                    h5_file = h5py.File(self.database, 'r')

            readcalib = read_calibration.ReadCalibration('vega', filter_name=None)
            calibbox = readcalib.get_spectrum()

            if wavel_resample is not None:
                new_spec_wavs = wavel_resample
            else:
                new_spec_wavs = self.wl_points

            flux_vega, _ = spectres.spectres(new_spec_wavs,
                                             calibbox.wavelength,
                                             calibbox.flux,
                                             spec_errs=calibbox.error,
                                             fill=np.nan,
                                             verbose=True)

            flux = -2.5*np.log10(flux/flux_vega)

        else:
            quantity = 'flux'

        if np.isnan(np.sum(flux)):
            warnings.warn(f'The resampled spectrum contains {np.sum(np.isnan(flux))} NaNs, '
                          f'probably because the original wavelength range does not fully '
                          f'encompass the new wavelength range. The happened with the '
                          f'following parameters: {model_param}.')

        if wavel_resample is None:
            wavelength = self.wl_points
        else:
            wavelength = wavel_resample

        # is_finite = np.where(np.isfinite(flux))[0]
        #
        # if wavel_resample is None:
        #     wavelength = self.wl_points[is_finite]
        # else:
        #     wavelength = wavel_resample[is_finite]
        #
        # if wavelength.shape[0] == 0:
        #     raise ValueError(f'The model spectrum is empty. Perhaps the grid could not be '
        #                      f'interpolated at {model_param} because zeros are stored in the '
        #                      f'database.')

        model_box = box.create_box(boxtype='model',
                                   model=self.model,
                                   wavelength=wavelength,
                                   flux=flux,
                                   parameters=model_param,
                                   quantity=quantity)

        if 'lognorm_radius' in model_param and 'lognorm_sigma' in model_param and \
                'lognorm_ext' in model_param:

            model_box.flux = self.apply_lognorm_ext(model_box.wavelength,
                                                    model_box.flux,
                                                    model_param['lognorm_radius'],
                                                    model_param['lognorm_sigma'],
                                                    model_param['lognorm_ext'])

        if 'powerlaw_max' in model_param and 'powerlaw_exp' in model_param and \
                'powerlaw_ext' in model_param:

            model_box.flux = self.apply_powerlaw_ext(model_box.wavelength,
                                                     model_box.flux,
                                                     model_param['powerlaw_max'],
                                                     model_param['powerlaw_exp'],
                                                     model_param['powerlaw_ext'])

        if 'ism_ext' in model_param and 'ism_red' in model_param:

            model_box.flux = self.apply_ism_ext(model_box.wavelength,
                                                model_box.flux,
                                                model_param['ism_ext'],
                                                model_param['ism_red'])

        if 'radius' in model_box.parameters:
            model_box.parameters['luminosity'] = 4. * np.pi * (
                model_box.parameters['radius'] * constants.R_JUP)**2 * constants.SIGMA_SB * \
                model_box.parameters['teff']**4. / constants.L_SUN  # (Lsun)

        return model_box
Пример #15
0
    def get_spectrum(self,
                     sptypes=None,
                     exclude_nan=True):
        """
        Function for selecting spectra from the database.

        Parameters
        ----------
        sptypes : list('str', )
            Spectral types to select from a library. The spectral types should be indicated with
            two characters (e.g. 'M5', 'L2', 'T3'). All spectra are selected if set to None.
        exclude_nan : bool
            Exclude wavelength points for which the flux is NaN.

        Returns
        -------
        species.core.box.SpectrumBox
            Box with the spectra.
        """

        h5_file = h5py.File(self.database, 'r')

        try:
            h5_file[f'spectra/{self.spec_library}']

        except KeyError:
            h5_file.close()
            species_db = database.Database()
            species_db.add_spectrum(self.spec_library, sptypes)
            h5_file = h5py.File(self.database, 'r')

        list_wavelength = []
        list_flux = []
        list_error = []
        list_name = []
        list_simbad = []
        list_sptype = []
        list_distance = []

        for item in h5_file[f'spectra/{self.spec_library}']:
            data = h5_file[f'spectra/{self.spec_library}/{item}']

            wavelength = data[0, :]  # (um)
            flux = data[1, :]  # (W m-2 um-1)
            error = data[2, :]  # (W m-2 um-1)

            if exclude_nan:
                indices = np.isnan(flux)
                indices = np.logical_not(indices)
                indices = np.where(indices)[0]

                wavelength = wavelength[indices]
                flux = flux[indices]
                error = error[indices]

            if self.wavel_range is None:
                wl_index = np.arange(0, len(wavelength), 1)

            else:
                wl_index = (flux > 0.) & (wavelength > self.wavel_range[0]) & \
                           (wavelength < self.wavel_range[1])

            count = np.count_nonzero(wl_index)

            if count > 0:
                index = np.where(wl_index)[0]

                if index[0] > 0:
                    wl_index[index[0] - 1] = True

                if index[-1] < len(wl_index)-1:
                    wl_index[index[-1] + 1] = True

                list_wavelength.append(wavelength[wl_index])
                list_flux.append(flux[wl_index])
                list_error.append(error[wl_index])

                attrs = data.attrs

                if 'name' in attrs:
                    list_name.append(data.attrs['name'].decode('utf-8'))
                else:
                    list_name.append('')

                if 'simbad' in attrs:
                    list_simbad.append(data.attrs['simbad'].decode('utf-8'))
                else:
                    list_simbad.append('')

                if 'sptype' in attrs:
                    list_sptype.append(data.attrs['sptype'].decode('utf-8'))
                else:
                    list_sptype.append('None')

                if 'distance' in attrs:
                    list_distance.append((data.attrs['distance'], data.attrs['distance_error']))
                else:
                    list_distance.append((np.nan, np.nan))

            else:
                list_wavelength.append(np.array([]))
                list_flux.append(np.array([]))
                list_error.append(np.array([]))
                list_name.append('')
                list_simbad.append('')
                list_sptype.append('None')
                list_distance.append((np.nan, np.nan))

        specbox = box.SpectrumBox()

        specbox.spec_library = self.spec_library
        specbox.wavelength = np.asarray(list_wavelength)
        specbox.flux = np.asarray(list_flux)
        specbox.error = np.asarray(list_error)
        specbox.name = np.asarray(list_name)
        specbox.simbad = np.asarray(list_simbad)
        specbox.sptype = np.asarray(list_sptype)
        specbox.distance = np.asarray(list_distance)

        if sptypes is not None:
            indices = None

            for item in sptypes:
                if indices is None:
                    indices = np.where(np.chararray.startswith(specbox.sptype, item))[0]

                else:
                    ind_tmp = np.where(np.chararray.startswith(specbox.sptype, item))[0]
                    indices = np.append(indices, ind_tmp)

            specbox.wavelength = specbox.wavelength[indices]
            specbox.flux = specbox.flux[indices]
            specbox.error = specbox.error[indices]
            specbox.name = specbox.name[indices]
            specbox.simbad = specbox.simbad[indices]
            specbox.sptype = specbox.sptype[indices]
            specbox.distance = specbox.distance[indices]

        return specbox
Пример #16
0
def plot_extinction(tag: str,
                    burnin: Optional[int] = None,
                    random: Optional[int] = None,
                    wavel_range: Optional[Tuple[float, float]] = None,
                    xlim: Optional[Tuple[float, float]] = None,
                    ylim: Optional[Tuple[float, float]] = None,
                    offset: Optional[Tuple[float, float]] = None,
                    output: str = 'extinction.pdf') -> None:
    """
    Function to plot random samples of the extinction, either from fitting a size distribution
    of enstatite grains (``dust_radius``, ``dust_sigma``, and ``dust_ext``), or from fitting
    ISM extinction (``ism_ext`` and optionally ``ism_red``).

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``. Only required
        after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`.
    random : int, None
        Number of randomly selected samples. All samples are used if set to ``None``.
    wavel_range : tuple(float, float), None
        Wavelength range (um) for the extinction. The default wavelength range (0.4, 10.) is used
        if set to ``None``.
    xlim : tuple(float, float), None
        Limits of the wavelength axis. The range is set automatically if set to ``None``.
    ylim : tuple(float, float)
        Limits of the extinction axis. The range is set automatically if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    output : str
        Output filename.

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

    if burnin is None:
        burnin = 0

    if wavel_range is None:
        wavel_range = (0.4, 10.)

    mpl.rcParams['font.serif'] = ['Bitstream Vera Serif']
    mpl.rcParams['font.family'] = 'serif'

    plt.rc('axes', edgecolor='black', linewidth=2.2)

    species_db = database.Database()
    box = species_db.get_samples(tag)

    samples = box.samples

    if samples.ndim == 2 and random is not None:
        ran_index = np.random.randint(samples.shape[0], size=random)
        samples = samples[ran_index, ]

    elif samples.ndim == 3:
        if burnin > samples.shape[1]:
            raise ValueError(
                f'The \'burnin\' value is larger than the number of steps '
                f'({samples.shape[1]}) that are made by the walkers.')

        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, :]

    plt.figure(1, figsize=(6, 3))
    gridsp = mpl.gridspec.GridSpec(1, 1)
    gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1)

    ax = plt.subplot(gridsp[0, 0])

    ax.tick_params(axis='both',
                   which='major',
                   colors='black',
                   labelcolor='black',
                   direction='in',
                   width=1,
                   length=5,
                   labelsize=12,
                   top=True,
                   bottom=True,
                   left=True,
                   right=True,
                   labelbottom=True)

    ax.tick_params(axis='both',
                   which='minor',
                   colors='black',
                   labelcolor='black',
                   direction='in',
                   width=1,
                   length=3,
                   labelsize=12,
                   top=True,
                   bottom=True,
                   left=True,
                   right=True,
                   labelbottom=True)

    ax.set_xlabel('Wavelength (µm)', fontsize=12)
    ax.set_ylabel('Extinction (mag)', fontsize=12)

    if xlim is not None:
        ax.set_xlim(xlim[0], xlim[1])

    if ylim is not None:
        ax.set_ylim(ylim[0], ylim[1])

    if offset is not None:
        ax.get_xaxis().set_label_coords(0.5, offset[0])
        ax.get_yaxis().set_label_coords(offset[1], 0.5)

    else:
        ax.get_xaxis().set_label_coords(0.5, -0.22)
        ax.get_yaxis().set_label_coords(-0.09, 0.5)

    sample_wavel = np.linspace(wavel_range[0], wavel_range[1], 100)

    if 'lognorm_radius' in box.parameters and 'lognorm_sigma' in box.parameters and \
            'lognorm_ext' in box.parameters:

        cross_optical, dust_radius, dust_sigma = dust_util.interp_lognorm([],
                                                                          [],
                                                                          None)

        log_r_index = box.parameters.index('lognorm_radius')
        sigma_index = box.parameters.index('lognorm_sigma')
        ext_index = box.parameters.index('lognorm_ext')

        log_r_g = samples[:, log_r_index]
        sigma_g = samples[:, sigma_index]
        dust_ext = samples[:, ext_index]

        database_path = dust_util.check_dust_database()

        with h5py.File(database_path, 'r') as h5_file:
            cross_section = np.asarray(
                h5_file['dust/lognorm/mgsio3/crystalline/cross_section'])
            wavelength = np.asarray(
                h5_file['dust/lognorm/mgsio3/crystalline/wavelength'])

        cross_interp = RegularGridInterpolator(
            (wavelength, dust_radius, dust_sigma), cross_section)

        for i in range(samples.shape[0]):
            cross_tmp = cross_optical['Generic/Bessell.V'](sigma_g[i],
                                                           10.**log_r_g[i])

            n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.))

            sample_cross = np.zeros(sample_wavel.shape)

            for j, item in enumerate(sample_wavel):
                sample_cross[j] = cross_interp(
                    (item, 10.**log_r_g[i], sigma_g[i]))

            sample_ext = 2.5 * np.log10(np.exp(1.)) * sample_cross * n_grains

            ax.plot(sample_wavel,
                    sample_ext,
                    ls='-',
                    lw=0.5,
                    color='black',
                    alpha=0.5)

    elif 'powerlaw_max' in box.parameters and 'powerlaw_exp' in box.parameters and \
            'powerlaw_ext' in box.parameters:

        cross_optical, dust_max, dust_exp = dust_util.interp_powerlaw([], [],
                                                                      None)

        r_max_index = box.parameters.index('powerlaw_max')
        exp_index = box.parameters.index('powerlaw_exp')
        ext_index = box.parameters.index('powerlaw_ext')

        r_max = samples[:, r_max_index]
        exponent = samples[:, exp_index]
        dust_ext = samples[:, ext_index]

        database_path = dust_util.check_dust_database()

        with h5py.File(database_path, 'r') as h5_file:
            cross_section = np.asarray(
                h5_file['dust/powerlaw/mgsio3/crystalline/cross_section'])
            wavelength = np.asarray(
                h5_file['dust/powerlaw/mgsio3/crystalline/wavelength'])

        cross_interp = RegularGridInterpolator(
            (wavelength, dust_max, dust_exp), cross_section)

        for i in range(samples.shape[0]):
            cross_tmp = cross_optical['Generic/Bessell.V'](exponent[i],
                                                           10.**r_max[i])

            n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.))

            sample_cross = np.zeros(sample_wavel.shape)

            for j, item in enumerate(sample_wavel):
                sample_cross[j] = cross_interp(
                    (item, 10.**r_max[i], exponent[i]))

            sample_ext = 2.5 * np.log10(np.exp(1.)) * sample_cross * n_grains

            ax.plot(sample_wavel,
                    sample_ext,
                    ls='-',
                    lw=0.5,
                    color='black',
                    alpha=0.5)

    elif 'ism_ext' in box.parameters:

        ext_index = box.parameters.index('ism_ext')
        ism_ext = samples[:, ext_index]

        if 'ism_red' in box.parameters:
            red_index = box.parameters.index('ism_red')
            ism_red = samples[:, red_index]

        else:
            ism_red = np.full(samples.shape[0], 3.1)

        for i in range(samples.shape[0]):
            sample_ext = dust_util.ism_extinction(ism_ext[i], ism_red[i],
                                                  sample_wavel)

            ax.plot(sample_wavel,
                    sample_ext,
                    ls='-',
                    lw=0.5,
                    color='black',
                    alpha=0.5)

    else:
        raise ValueError(
            'The SamplesBox does not contain extinction parameters.')

    print(f'Plotting extinction: {output}...', end='', flush=True)

    plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight')
    plt.clf()
    plt.close()

    print(' [DONE]')
Пример #17
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')
Пример #18
0
def plot_extinction(
    tag: str,
    burnin: Optional[int] = None,
    random: Optional[int] = None,
    wavel_range: Optional[Tuple[float, float]] = None,
    xlim: Optional[Tuple[float, float]] = None,
    ylim: Optional[Tuple[float, float]] = None,
    offset: Optional[Tuple[float, float]] = None,
    output: Optional[str] = "extinction.pdf",
) -> None:
    """
    Function to plot random samples of the extinction, either from fitting a size distribution
    of enstatite grains (``dust_radius``, ``dust_sigma``, and ``dust_ext``), or from fitting
    ISM extinction (``ism_ext`` and optionally ``ism_red``).

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``. Only required
        after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`.
    random : int, None
        Number of randomly selected samples. All samples are used if set to ``None``.
    wavel_range : tuple(float, float), None
        Wavelength range (um) for the extinction. The default wavelength range (0.4, 10.) is used
        if set to ``None``.
    xlim : tuple(float, float), None
        Limits of the wavelength axis. The range is set automatically if set to ``None``.
    ylim : tuple(float, float)
        Limits of the extinction axis. The range is set automatically if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    output : str
        Output filename for the plot. The plot is shown in an
        interface window if the argument is set to ``None``.

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

    if burnin is None:
        burnin = 0

    if wavel_range is None:
        wavel_range = (0.4, 10.0)

    mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"]
    mpl.rcParams["font.family"] = "serif"

    plt.rc("axes", edgecolor="black", linewidth=2.2)

    species_db = database.Database()
    box = species_db.get_samples(tag)

    samples = box.samples

    if samples.ndim == 2 and random is not None:
        ran_index = np.random.randint(samples.shape[0], size=random)
        samples = samples[ran_index, ]

    elif samples.ndim == 3:
        if burnin > samples.shape[1]:
            raise ValueError(
                f"The 'burnin' value is larger than the number of steps "
                f"({samples.shape[1]}) that are made by the walkers.")

        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, :]

    plt.figure(1, figsize=(6, 3))
    gridsp = mpl.gridspec.GridSpec(1, 1)
    gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1)

    ax = plt.subplot(gridsp[0, 0])

    ax.tick_params(
        axis="both",
        which="major",
        colors="black",
        labelcolor="black",
        direction="in",
        width=1,
        length=5,
        labelsize=12,
        top=True,
        bottom=True,
        left=True,
        right=True,
        labelbottom=True,
    )

    ax.tick_params(
        axis="both",
        which="minor",
        colors="black",
        labelcolor="black",
        direction="in",
        width=1,
        length=3,
        labelsize=12,
        top=True,
        bottom=True,
        left=True,
        right=True,
        labelbottom=True,
    )

    ax.set_xlabel("Wavelength (µm)", fontsize=12)
    ax.set_ylabel("Extinction (mag)", fontsize=12)

    if xlim is not None:
        ax.set_xlim(xlim[0], xlim[1])

    if ylim is not None:
        ax.set_ylim(ylim[0], ylim[1])

    if offset is not None:
        ax.get_xaxis().set_label_coords(0.5, offset[0])
        ax.get_yaxis().set_label_coords(offset[1], 0.5)

    else:
        ax.get_xaxis().set_label_coords(0.5, -0.22)
        ax.get_yaxis().set_label_coords(-0.09, 0.5)

    sample_wavel = np.linspace(wavel_range[0], wavel_range[1], 100)

    if ("lognorm_radius" in box.parameters
            and "lognorm_sigma" in box.parameters
            and "lognorm_ext" in box.parameters):

        cross_optical, dust_radius, dust_sigma = dust_util.interp_lognorm([],
                                                                          [],
                                                                          None)

        log_r_index = box.parameters.index("lognorm_radius")
        sigma_index = box.parameters.index("lognorm_sigma")
        ext_index = box.parameters.index("lognorm_ext")

        log_r_g = samples[:, log_r_index]
        sigma_g = samples[:, sigma_index]
        dust_ext = samples[:, ext_index]

        database_path = dust_util.check_dust_database()

        with h5py.File(database_path, "r") as h5_file:
            cross_section = np.asarray(
                h5_file["dust/lognorm/mgsio3/crystalline/cross_section"])
            wavelength = np.asarray(
                h5_file["dust/lognorm/mgsio3/crystalline/wavelength"])

        cross_interp = RegularGridInterpolator(
            (wavelength, dust_radius, dust_sigma), cross_section)

        for i in range(samples.shape[0]):
            cross_tmp = cross_optical["Generic/Bessell.V"](sigma_g[i],
                                                           10.0**log_r_g[i])

            n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.0))

            sample_cross = np.zeros(sample_wavel.shape)

            for j, item in enumerate(sample_wavel):
                sample_cross[j] = cross_interp(
                    (item, 10.0**log_r_g[i], sigma_g[i]))

            sample_ext = 2.5 * np.log10(np.exp(1.0)) * sample_cross * n_grains

            ax.plot(sample_wavel,
                    sample_ext,
                    ls="-",
                    lw=0.5,
                    color="black",
                    alpha=0.5)

    elif ("powerlaw_max" in box.parameters and "powerlaw_exp" in box.parameters
          and "powerlaw_ext" in box.parameters):

        cross_optical, dust_max, dust_exp = dust_util.interp_powerlaw([], [],
                                                                      None)

        r_max_index = box.parameters.index("powerlaw_max")
        exp_index = box.parameters.index("powerlaw_exp")
        ext_index = box.parameters.index("powerlaw_ext")

        r_max = samples[:, r_max_index]
        exponent = samples[:, exp_index]
        dust_ext = samples[:, ext_index]

        database_path = dust_util.check_dust_database()

        with h5py.File(database_path, "r") as h5_file:
            cross_section = np.asarray(
                h5_file["dust/powerlaw/mgsio3/crystalline/cross_section"])
            wavelength = np.asarray(
                h5_file["dust/powerlaw/mgsio3/crystalline/wavelength"])

        cross_interp = RegularGridInterpolator(
            (wavelength, dust_max, dust_exp), cross_section)

        for i in range(samples.shape[0]):
            cross_tmp = cross_optical["Generic/Bessell.V"](exponent[i],
                                                           10.0**r_max[i])

            n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.0))

            sample_cross = np.zeros(sample_wavel.shape)

            for j, item in enumerate(sample_wavel):
                sample_cross[j] = cross_interp(
                    (item, 10.0**r_max[i], exponent[i]))

            sample_ext = 2.5 * np.log10(np.exp(1.0)) * sample_cross * n_grains

            ax.plot(sample_wavel,
                    sample_ext,
                    ls="-",
                    lw=0.5,
                    color="black",
                    alpha=0.5)

    elif "ism_ext" in box.parameters:

        ext_index = box.parameters.index("ism_ext")
        ism_ext = samples[:, ext_index]

        if "ism_red" in box.parameters:
            red_index = box.parameters.index("ism_red")
            ism_red = samples[:, red_index]

        else:
            # Use default ISM redenning (R_V = 3.1) if ism_red was not fitted
            ism_red = np.full(samples.shape[0], 3.1)

        for i in range(samples.shape[0]):
            sample_ext = dust_util.ism_extinction(ism_ext[i], ism_red[i],
                                                  sample_wavel)

            ax.plot(sample_wavel,
                    sample_ext,
                    ls="-",
                    lw=0.5,
                    color="black",
                    alpha=0.5)

    else:
        raise ValueError(
            "The SamplesBox does not contain extinction parameters.")

    if output is None:
        print("Plotting extinction...", end="", flush=True)
    else:
        print(f"Plotting extinction: {output}...", end="", flush=True)

    print(" [DONE]")

    if output is None:
        plt.show()
    else:
        plt.savefig(output, bbox_inches="tight")

    plt.clf()
    plt.close()
Пример #19
0
def plot_size_distributions(tag: str,
                            burnin: Optional[int] = None,
                            random: Optional[int] = None,
                            offset: Optional[Tuple[float, float]] = None,
                            output: str = 'size_distributions.pdf') -> None:
    """
    Function to plot random samples of the log-normal or power-law size distribution.

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``. Only required
        after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`.
    random : int, None
        Number of randomly selected samples. All samples are used if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    output : str
        Output filename.

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

    print(f'Plotting size distributions: {output}...', end='', flush=True)

    if burnin is None:
        burnin = 0

    mpl.rcParams['font.serif'] = ['Bitstream Vera Serif']
    mpl.rcParams['font.family'] = 'serif'

    plt.rc('axes', edgecolor='black', linewidth=2.2)

    species_db = database.Database()
    box = species_db.get_samples(tag)

    if 'lognorm_radius' not in box.parameters and 'powerlaw_max' not in box.parameters:
        raise ValueError(
            'The SamplesBox does not contain extinction parameter for a log-normal '
            'or power-law size distribution.')

    samples = box.samples

    if samples.ndim == 2 and random is not None:
        ran_index = np.random.randint(samples.shape[0], size=random)
        samples = samples[ran_index, ]

    elif samples.ndim == 3:
        if burnin > samples.shape[1]:
            raise ValueError(
                f'The \'burnin\' value is larger than the number of steps '
                f'({samples.shape[1]}) that are made by the walkers.')

        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, :]

    if 'lognorm_radius' in box.parameters:
        log_r_index = box.parameters.index('lognorm_radius')
        sigma_index = box.parameters.index('lognorm_sigma')

        log_r_g = samples[:, log_r_index]
        sigma_g = samples[:, sigma_index]

    if 'powerlaw_max' in box.parameters:
        r_max_index = box.parameters.index('powerlaw_max')
        exponent_index = box.parameters.index('powerlaw_exp')

        r_max = samples[:, r_max_index]
        exponent = samples[:, exponent_index]

    plt.figure(1, figsize=(6, 3))
    gridsp = mpl.gridspec.GridSpec(1, 1)
    gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1)

    ax = plt.subplot(gridsp[0, 0])

    ax.tick_params(axis='both',
                   which='major',
                   colors='black',
                   labelcolor='black',
                   direction='in',
                   width=1,
                   length=5,
                   labelsize=12,
                   top=True,
                   bottom=True,
                   left=True,
                   right=True,
                   labelbottom=True)

    ax.tick_params(axis='both',
                   which='minor',
                   colors='black',
                   labelcolor='black',
                   direction='in',
                   width=1,
                   length=3,
                   labelsize=12,
                   top=True,
                   bottom=True,
                   left=True,
                   right=True,
                   labelbottom=True)

    ax.set_xlabel('Grain size (µm)', fontsize=12)
    ax.set_ylabel('dn/dr', fontsize=12)

    ax.set_xscale('log')

    if 'powerlaw_max' in box.parameters:
        ax.set_yscale('log')

    if offset is not None:
        ax.get_xaxis().set_label_coords(0.5, offset[0])
        ax.get_yaxis().set_label_coords(offset[1], 0.5)

    else:
        ax.get_xaxis().set_label_coords(0.5, -0.22)
        ax.get_yaxis().set_label_coords(-0.09, 0.5)

    for i in range(samples.shape[0]):
        if 'lognorm_radius' in box.parameters:
            dn_dr, _, radii = dust_util.log_normal_distribution(
                10.**log_r_g[i], sigma_g[i], 1000)

        elif 'powerlaw_max' in box.parameters:
            dn_dr, _, radii = dust_util.power_law_distribution(
                exponent[i], 1e-3, 10.**r_max[i], 1000)

        ax.plot(radii, dn_dr, ls='-', lw=0.5, color='black', alpha=0.5)

    plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight')
    plt.clf()
    plt.close()

    print(' [DONE]')
Пример #20
0
def plot_posterior(tag: str,
                   burnin: Optional[int] = None,
                   title: Optional[str] = None,
                   offset: Optional[Tuple[float, float]] = None,
                   title_fmt: Union[str, List[str]] = '.2f',
                   limits: Optional[List[Tuple[float, float]]] = None,
                   max_posterior: bool = False,
                   inc_luminosity: bool = False,
                   inc_mass: bool = False,
                   output: str = 'posterior.pdf') -> None:
    """
    Function to plot the posterior distribution.

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``.
    title : str, None
        Plot title. No title is shown if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    title_fmt : str, list(str)
        Format of the titles above the 1D distributions. Either a single string, which will be used
        for all parameters, or a list with the title format for each parameter separately (in the
        order as shown in the corner plot).
    limits : list(tuple(float, float), ), None
        Axis limits of all parameters. Automatically set if set to ``None``.
    max_posterior : bool
        Plot the position of the sample with the maximum posterior probability.
    inc_luminosity : bool
        Include the log10 of the luminosity in the posterior plot as calculated from the
        effective temperature and radius.
    inc_mass : bool
        Include the mass in the posterior plot as calculated from the surface gravity and radius.
    output : str
        Output filename.

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

    mpl.rcParams['font.serif'] = ['Bitstream Vera Serif']
    mpl.rcParams['font.family'] = 'serif'

    plt.rc('axes', edgecolor='black', linewidth=2.2)

    if burnin is None:
        burnin = 0

    species_db = database.Database()
    box = species_db.get_samples(tag, burnin=burnin)

    print('Median sample:')
    for key, value in box.median_sample.items():
        print(f'   - {key} = {value:.2f}')

    samples = box.samples
    ndim = samples.shape[-1]

    if box.prob_sample is not None:
        par_val = tuple(box.prob_sample.values())

        print('Maximum posterior sample:')
        for key, value in box.prob_sample.items():
            print(f'   - {key} = {value:.2f}')

    print(f'Plotting the posterior: {output}...', end='', flush=True)

    if inc_luminosity:
        if 'teff' in box.parameters and 'radius' in box.parameters:
            teff_index = np.argwhere(np.array(box.parameters) == 'teff')[0]
            radius_index = np.argwhere(np.array(box.parameters) == 'radius')[0]

            luminosity = 4. * np.pi * (samples[..., radius_index]*constants.R_JUP)**2 * \
                constants.SIGMA_SB * samples[..., teff_index]**4. / constants.L_SUN

            samples = np.append(samples, np.log10(luminosity), axis=-1)
            box.parameters.append('luminosity')
            ndim += 1

        elif 'teff_0' in box.parameters and 'radius_0' in box.parameters:
            luminosity = 0.

            for i in range(100):
                teff_index = np.argwhere(
                    np.array(box.parameters) == f'teff_{i}')
                radius_index = np.argwhere(
                    np.array(box.parameters) == f'radius_{i}')

                if len(teff_index) > 0 and len(radius_index) > 0:
                    luminosity += 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \
                        * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN

                else:
                    break

            samples = np.append(samples, np.log10(luminosity), axis=-1)
            box.parameters.append('luminosity')
            ndim += 1

            # teff_index = np.argwhere(np.array(box.parameters) == 'teff_0')
            # radius_index = np.argwhere(np.array(box.parameters) == 'radius_0')
            #
            # luminosity_0 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN
            #
            # samples = np.append(samples, np.log10(luminosity_0), axis=-1)
            # box.parameters.append('luminosity_0')
            # ndim += 1
            #
            # teff_index = np.argwhere(np.array(box.parameters) == 'teff_1')
            # radius_index = np.argwhere(np.array(box.parameters) == 'radius_1')
            #
            # luminosity_1 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN
            #
            # samples = np.append(samples, np.log10(luminosity_1), axis=-1)
            # box.parameters.append('luminosity_1')
            # ndim += 1
            #
            # teff_index_0 = np.argwhere(np.array(box.parameters) == 'teff_0')
            # radius_index_0 = np.argwhere(np.array(box.parameters) == 'radius_0')
            #
            # teff_index_1 = np.argwhere(np.array(box.parameters) == 'teff_1')
            # radius_index_1 = np.argwhere(np.array(box.parameters) == 'radius_1')
            #
            # luminosity_0 = 4. * np.pi * (samples[..., radius_index_0[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index_0[0]]**4. / constants.L_SUN
            #
            # luminosity_1 = 4. * np.pi * (samples[..., radius_index_1[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index_1[0]]**4. / constants.L_SUN
            #
            # samples = np.append(samples, np.log10(luminosity_0/luminosity_1), axis=-1)
            # box.parameters.append('luminosity_ratio')
            # ndim += 1

            # r_tmp = samples[..., radius_index_0[0]]*constants.R_JUP
            # lum_diff = (luminosity_1*constants.L_SUN-luminosity_0*constants.L_SUN)
            #
            # m_mdot = (3600.*24.*365.25)*lum_diff*r_tmp/constants.GRAVITY/constants.M_JUP**2
            #
            # samples = np.append(samples, m_mdot, axis=-1)
            # box.parameters.append('m_mdot')
            # ndim += 1

    if inc_mass:
        if 'logg' in box.parameters and 'radius' in box.parameters:
            logg_index = np.argwhere(np.array(box.parameters) == 'logg')[0]
            radius_index = np.argwhere(np.array(box.parameters) == 'radius')[0]

            mass_samples = read_util.get_mass(samples[..., logg_index],
                                              samples[..., radius_index])

            samples = np.append(samples, mass_samples, axis=-1)
            box.parameters.append('mass')
            ndim += 1

        else:
            warnings.warn(
                'Samples with the log(g) and radius are required for \'inc_mass=True\'.'
            )

    if isinstance(title_fmt, list) and len(title_fmt) != ndim:
        raise ValueError(
            f'The number of items in the list of \'title_fmt\' ({len(title_fmt)}) is '
            f'not equal to the number of dimensions of the samples ({ndim}).')

    labels = plot_util.update_labels(box.parameters)

    # Check if parameter values were fixed

    index_sel = []
    index_del = []

    # Use only last axis for parameter dimensions
    for i in range(ndim):
        if np.amin(samples[..., i]) == np.amax(samples[..., i]):
            index_del.append(i)
        else:
            index_sel.append(i)

    samples = samples[..., index_sel]

    for i in range(len(index_del) - 1, -1, -1):
        del labels[index_del[i]]

    ndim -= len(index_del)

    samples = samples.reshape((-1, ndim))

    hist_titles = []

    for i, item in enumerate(labels):
        unit_start = item.find('(')

        if unit_start == -1:
            param_label = item
            unit_label = None

        else:
            param_label = item[:unit_start]
            # Remove parenthesis from the units
            unit_label = item[unit_start + 1:-1]

        q_16, q_50, q_84 = corner.quantile(samples[:, i], [0.16, 0.5, 0.84])
        q_minus, q_plus = q_50 - q_16, q_84 - q_50

        if isinstance(title_fmt, str):
            fmt = '{{0:{0}}}'.format(title_fmt).format

        elif isinstance(title_fmt, list):
            fmt = '{{0:{0}}}'.format(title_fmt[i]).format

        best_fit = r'${{{0}}}_{{-{1}}}^{{+{2}}}$'
        best_fit = best_fit.format(fmt(q_50), fmt(q_minus), fmt(q_plus))

        if unit_label is None:
            hist_title = f'{param_label} = {best_fit}'

        else:
            hist_title = f'{param_label} = {best_fit} {unit_label}'

        hist_titles.append(hist_title)

    fig = corner.corner(samples,
                        quantiles=[0.16, 0.5, 0.84],
                        labels=labels,
                        label_kwargs={'fontsize': 13},
                        titles=hist_titles,
                        show_titles=True,
                        title_fmt=None,
                        title_kwargs={'fontsize': 12})

    axes = np.array(fig.axes).reshape((ndim, ndim))

    for i in range(ndim):
        for j in range(ndim):
            if i >= j:
                ax = axes[i, j]

                ax.xaxis.set_major_formatter(ScalarFormatter(useOffset=False))
                ax.yaxis.set_major_formatter(ScalarFormatter(useOffset=False))

                labelleft = j == 0 and i != 0
                labelbottom = i == ndim - 1

                ax.tick_params(axis='both',
                               which='major',
                               colors='black',
                               labelcolor='black',
                               direction='in',
                               width=1,
                               length=5,
                               labelsize=12,
                               top=True,
                               bottom=True,
                               left=True,
                               right=True,
                               labelleft=labelleft,
                               labelbottom=labelbottom,
                               labelright=False,
                               labeltop=False)

                ax.tick_params(axis='both',
                               which='minor',
                               colors='black',
                               labelcolor='black',
                               direction='in',
                               width=1,
                               length=3,
                               labelsize=12,
                               top=True,
                               bottom=True,
                               left=True,
                               right=True,
                               labelleft=labelleft,
                               labelbottom=labelbottom,
                               labelright=False,
                               labeltop=False)

                if limits is not None:
                    ax.set_xlim(limits[j])

                if max_posterior:
                    ax.axvline(par_val[j], color='tomato')

                if i > j:
                    if max_posterior:
                        ax.axhline(par_val[i], color='tomato')
                        ax.plot(par_val[j], par_val[i], 's', color='tomato')

                    if limits is not None:
                        ax.set_ylim(limits[i])

                if offset is not None:
                    ax.get_xaxis().set_label_coords(0.5, offset[0])
                    ax.get_yaxis().set_label_coords(offset[1], 0.5)

                else:
                    ax.get_xaxis().set_label_coords(0.5, -0.26)
                    ax.get_yaxis().set_label_coords(-0.27, 0.5)

    if title:
        fig.suptitle(title, y=1.02, fontsize=16)

    plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight')
    plt.clf()
    plt.close()

    print(' [DONE]')
Пример #21
0
def plot_walkers(tag: str,
                 nsteps: Optional[int] = None,
                 offset: Optional[Tuple[float, float]] = None,
                 output: str = 'walkers.pdf') -> None:
    """
    Function to plot the step history of the walkers.

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    nsteps : int, None
        Number of steps that are plotted. All steps are plotted if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    output : str
        Output filename.

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

    print(f'Plotting walkers: {output}...', end='', flush=True)

    mpl.rcParams['font.serif'] = ['Bitstream Vera Serif']
    mpl.rcParams['font.family'] = 'serif'

    plt.rc('axes', edgecolor='black', linewidth=2.2)

    species_db = database.Database()
    box = species_db.get_samples(tag)

    samples = box.samples
    labels = plot_util.update_labels(box.parameters)

    if samples.ndim == 2:
        raise ValueError(
            f'The samples of \'{tag}\' have only 2 dimensions whereas 3 are required '
            f'for plotting the walkers. The plot_walkers function can only be '
            f'used after running the MCMC with run_mcmc and not after running '
            f'MultiNest with run_multinest.')

    ndim = samples.shape[-1]

    plt.figure(1, figsize=(6, ndim * 1.5))
    gridsp = mpl.gridspec.GridSpec(ndim, 1)
    gridsp.update(wspace=0, hspace=0.1, left=0, right=1, bottom=0, top=1)

    for i in range(ndim):
        ax = plt.subplot(gridsp[i, 0])

        if i == ndim - 1:
            ax.tick_params(axis='both',
                           which='major',
                           colors='black',
                           labelcolor='black',
                           direction='in',
                           width=1,
                           length=5,
                           labelsize=12,
                           top=True,
                           bottom=True,
                           left=True,
                           right=True,
                           labelbottom=True)

            ax.tick_params(axis='both',
                           which='minor',
                           colors='black',
                           labelcolor='black',
                           direction='in',
                           width=1,
                           length=3,
                           labelsize=12,
                           top=True,
                           bottom=True,
                           left=True,
                           right=True,
                           labelbottom=True)

        else:
            ax.tick_params(axis='both',
                           which='major',
                           colors='black',
                           labelcolor='black',
                           direction='in',
                           width=1,
                           length=5,
                           labelsize=12,
                           top=True,
                           bottom=True,
                           left=True,
                           right=True,
                           labelbottom=False)

            ax.tick_params(axis='both',
                           which='minor',
                           colors='black',
                           labelcolor='black',
                           direction='in',
                           width=1,
                           length=3,
                           labelsize=12,
                           top=True,
                           bottom=True,
                           left=True,
                           right=True,
                           labelbottom=False)

        if i == ndim - 1:
            ax.set_xlabel('Step number', fontsize=10)
        else:
            ax.set_xlabel('', fontsize=10)

        ax.set_ylabel(labels[i], fontsize=10)

        if offset is not None:
            ax.get_xaxis().set_label_coords(0.5, offset[0])
            ax.get_yaxis().set_label_coords(offset[1], 0.5)

        else:
            ax.get_xaxis().set_label_coords(0.5, -0.22)
            ax.get_yaxis().set_label_coords(-0.09, 0.5)

        if nsteps is not None:
            ax.set_xlim(0, nsteps)

        for j in range(samples.shape[0]):
            ax.plot(samples[j, :, i], ls='-', lw=0.5, color='black', alpha=0.5)

    plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight')
    plt.clf()
    plt.close()

    print(' [DONE]')
Пример #22
0
def plot_size_distributions(
    tag: str,
    burnin: Optional[int] = None,
    random: Optional[int] = None,
    offset: Optional[Tuple[float, float]] = None,
    output: Optional[str] = "size_distributions.pdf",
) -> None:
    """
    Function to plot random samples of the log-normal or power-law size distributions.

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``. Only required
        after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`.
    random : int, None
        Number of randomly selected samples. All samples are used if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    output : str
        Output filename for the plot. The plot is shown in an
        interface window if the argument is set to ``None``.

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

    if output is None:
        print("Plotting size distributions...", end="", flush=True)
    else:
        print(f"Plotting size distributions: {output}...", end="", flush=True)

    if burnin is None:
        burnin = 0

    mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"]
    mpl.rcParams["font.family"] = "serif"

    plt.rc("axes", edgecolor="black", linewidth=2.2)

    species_db = database.Database()
    box = species_db.get_samples(tag)

    if "lognorm_radius" not in box.parameters and "powerlaw_max" not in box.parameters:
        raise ValueError(
            "The SamplesBox does not contain extinction parameter for a log-normal "
            "or power-law size distribution.")

    samples = box.samples

    if samples.ndim == 2 and random is not None:
        ran_index = np.random.randint(samples.shape[0], size=random)
        samples = samples[ran_index, ]

    elif samples.ndim == 3:
        if burnin > samples.shape[1]:
            raise ValueError(
                f"The 'burnin' value is larger than the number of steps "
                f"({samples.shape[1]}) that are made by the walkers.")

        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, :]

    if "lognorm_radius" in box.parameters:
        log_r_index = box.parameters.index("lognorm_radius")
        sigma_index = box.parameters.index("lognorm_sigma")

        log_r_g = samples[:, log_r_index]
        sigma_g = samples[:, sigma_index]

    if "powerlaw_max" in box.parameters:
        r_max_index = box.parameters.index("powerlaw_max")
        exponent_index = box.parameters.index("powerlaw_exp")

        r_max = samples[:, r_max_index]
        exponent = samples[:, exponent_index]

    plt.figure(1, figsize=(6, 3))
    gridsp = mpl.gridspec.GridSpec(1, 1)
    gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1)

    ax = plt.subplot(gridsp[0, 0])

    ax.tick_params(
        axis="both",
        which="major",
        colors="black",
        labelcolor="black",
        direction="in",
        width=1,
        length=5,
        labelsize=12,
        top=True,
        bottom=True,
        left=True,
        right=True,
        labelbottom=True,
    )

    ax.tick_params(
        axis="both",
        which="minor",
        colors="black",
        labelcolor="black",
        direction="in",
        width=1,
        length=3,
        labelsize=12,
        top=True,
        bottom=True,
        left=True,
        right=True,
        labelbottom=True,
    )

    ax.set_xlabel("Grain size (µm)", fontsize=12)
    ax.set_ylabel("dn/dr", fontsize=12)

    ax.set_xscale("log")

    if "powerlaw_max" in box.parameters:
        ax.set_yscale("log")

    if offset is not None:
        ax.get_xaxis().set_label_coords(0.5, offset[0])
        ax.get_yaxis().set_label_coords(offset[1], 0.5)

    else:
        ax.get_xaxis().set_label_coords(0.5, -0.22)
        ax.get_yaxis().set_label_coords(-0.09, 0.5)

    for i in range(samples.shape[0]):
        if "lognorm_radius" in box.parameters:
            dn_grains, r_width, radii = dust_util.log_normal_distribution(
                10.0**log_r_g[i], sigma_g[i], 1000)

            # Exclude radii smaller than 1 nm
            indices = np.argwhere(radii >= 1e-3)

            dn_grains = dn_grains[indices]
            r_width = r_width[indices]
            radii = radii[indices]

        elif "powerlaw_max" in box.parameters:
            dn_grains, r_width, radii = dust_util.power_law_distribution(
                exponent[i], 1e-3, 10.0**r_max[i], 1000)

        ax.plot(radii,
                dn_grains / r_width,
                ls="-",
                lw=0.5,
                color="black",
                alpha=0.5)

    print(" [DONE]")

    if output is None:
        plt.show()
    else:
        plt.savefig(output, bbox_inches="tight")

    plt.clf()
    plt.close()
Пример #23
0
    def run_mcmc(self,
                 nwalkers,
                 nsteps,
                 guess,
                 tag,
                 prior=None):
        """
        Function to run the MCMC sampler.

        Parameters
        ----------
        nwalkers : int
            Number of walkers.
        nsteps : int
            Number of steps per walker.
        guess : dict
            Guess for the parameter values. Random values between the boundary values are used
            if set to None.
        tag : str
            Database tag where the MCMC samples are stored.
        prior : tuple(str, float, float)
            Gaussian prior on one of the parameters. Currently only possible for the mass, e.g.
            ('mass', 13., 3.) for an expected mass of 13 Mjup with an uncertainty of 3 Mjup. Not
            used if set to None.

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

        global MIN_CHISQ
        global MIN_PARAM

        sigma = {'teff': 5., 'logg': 0.01, 'feh': 0.01, 'radius': 0.01}

        sys.stdout.write('Running MCMC...')
        sys.stdout.flush()

        ndim = len(self.bounds)

        initial = np.zeros((nwalkers, ndim))
        for i, item in enumerate(self.modelpar):
            if guess[item] is not None:
                initial[:, i] = guess[item] + np.random.normal(0, sigma[item], nwalkers)

            else:
                initial[:, i] = np.random.uniform(low=self.bounds[item][0],
                                                  high=self.bounds[item][1],
                                                  size=nwalkers)

        sampler = emcee.EnsembleSampler(nwalkers=nwalkers,
                                        dim=ndim,
                                        lnpostfn=lnprob,
                                        a=2.,
                                        args=([self.bounds,
                                               self.modelpar,
                                               self.modelphot,
                                               self.objphot,
                                               self.synphot,
                                               self.distance,
                                               prior,
                                               self.spectrum,
                                               self.instrument,
                                               self.modelspec]))

        progbar = progress.bar.Bar('\rRunning MCMC...',
                                   max=nsteps,
                                   suffix='%(percent)d%%')

        for i, _ in enumerate(sampler.sample(initial, iterations=nsteps)):
            progbar.next()

        progbar.finish()

        species_db = database.Database()

        species_db.add_samples(sampler=sampler,
                               spectrum=('model', self.model),
                               tag=tag,
                               chisquare=(MIN_CHISQ, MIN_PARAM),
                               modelpar=self.modelpar,
                               distance=self.distance)
Пример #24
0
def plot_mag_posterior(
    tag: str,
    filter_name: str,
    burnin: int = None,
    xlim: Tuple[float, float] = None,
    output: Optional[str] = "mag_posterior.pdf",
) -> np.ndarray:
    """
    Function to plot the posterior distribution of the synthetic
    magnitudes. The posterior samples are also returned.

    Parameters
    ----------
    tag : str
        Database tag with the posterior samples.
    filter_name : str
        Filter name.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``.
    xlim : tuple(float, float), None
        Axis limits. Automatically set if set to ``None``.
    output : str
        Output filename for the plot. The plot is shown in an
        interface window if the argument is set to ``None``.

    Returns
    -------
    np.ndarray
        Array with the posterior samples of the magnitude.
    """

    mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"]
    mpl.rcParams["font.family"] = "serif"

    plt.rc("axes", edgecolor="black", linewidth=2.2)

    species_db = database.Database()

    samples = species_db.get_mcmc_photometry(tag, filter_name, burnin)

    if output is None:
        print("Plotting photometry samples...", end="", flush=True)
    else:
        print(f"Plotting photometry samples: {output}...", end="", flush=True)

    fig = corner.corner(
        samples,
        labels=["Magnitude"],
        quantiles=[0.16, 0.5, 0.84],
        label_kwargs={"fontsize": 13.0},
        show_titles=True,
        title_kwargs={"fontsize": 12.0},
        title_fmt=".2f",
    )

    axes = np.array(fig.axes).reshape((1, 1))

    ax = axes[0, 0]

    ax.tick_params(
        axis="both",
        which="major",
        colors="black",
        labelcolor="black",
        direction="in",
        width=1,
        length=5,
        labelsize=12,
        top=True,
        bottom=True,
        left=True,
        right=True,
    )

    ax.tick_params(
        axis="both",
        which="minor",
        colors="black",
        labelcolor="black",
        direction="in",
        width=1,
        length=3,
        labelsize=12,
        top=True,
        bottom=True,
        left=True,
        right=True,
    )

    if xlim is not None:
        ax.set_xlim(xlim)

    ax.get_xaxis().set_label_coords(0.5, -0.26)

    print(" [DONE]")

    if output is None:
        plt.show()
    else:
        plt.savefig(output, bbox_inches="tight")

    plt.clf()
    plt.close()

    return samples
Пример #25
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,
        )
Пример #26
0
def plot_posterior(
    tag: str,
    burnin: Optional[int] = None,
    title: Optional[str] = None,
    offset: Optional[Tuple[float, float]] = None,
    title_fmt: Union[str, List[str]] = ".2f",
    limits: Optional[List[Tuple[float, float]]] = None,
    max_prob: bool = False,
    vmr: bool = False,
    inc_luminosity: bool = False,
    inc_mass: bool = False,
    inc_pt_param: bool = False,
    inc_loglike: bool = False,
    output: Optional[str] = "posterior.pdf",
) -> None:
    """
    Function to plot the posterior distribution of the fitted parameters.

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``.
    title : str, None
        Plot title. No title is shown if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    title_fmt : str, list(str)
        Format of the titles above the 1D distributions. Either a single string, which will be used
        for all parameters, or a list with the title format for each parameter separately (in the
        order as shown in the corner plot).
    limits : list(tuple(float, float), ), None
        Axis limits of all parameters. Automatically set if set to ``None``.
    max_prob : bool
        Plot the position of the sample with the maximum posterior probability.
    vmr : bool
        Plot the volume mixing ratios (i.e. number fractions) instead of the mass fractions of the
        retrieved species with :class:`~species.analysis.retrieval.AtmosphericRetrieval`.
    inc_luminosity : bool
        Include the log10 of the luminosity in the posterior plot as calculated from the
        effective temperature and radius.
    inc_mass : bool
        Include the mass in the posterior plot as calculated from the surface gravity and radius.
    inc_pt_param : bool
        Include the parameters of the pressure-temperature profile. Only used if the ``tag``
        contains samples obtained with :class:`~species.analysis.retrieval.AtmosphericRetrieval`.
    inc_loglike : bool
        Include the log10 of the likelihood as additional parameter in the corner plot.
    output : str
        Output filename for the plot. The plot is shown in an
        interface window if the argument is set to ``None``.

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

    mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"]
    mpl.rcParams["font.family"] = "serif"

    plt.rc("axes", edgecolor="black", linewidth=2.2)

    if burnin is None:
        burnin = 0

    species_db = database.Database()

    box = species_db.get_samples(tag, burnin=burnin)
    samples = box.samples

    # index_sel = [0, 1, 8, 9, 14]
    # samples = samples[:, index_sel]
    #
    # for i in range(13, 9, -1):
    #     del box.parameters[i]
    #
    # del box.parameters[2]
    # del box.parameters[2]
    # del box.parameters[2]
    # del box.parameters[2]
    # del box.parameters[2]
    # del box.parameters[2]

    ndim = len(box.parameters)

    if not inc_pt_param and box.spectrum == "petitradtrans":
        pt_param = ["tint", "t1", "t2", "t3", "alpha", "log_delta"]

        index_del = []
        item_del = []

        for i in range(100):
            pt_item = f"t{i}"

            if pt_item in box.parameters:
                param_index = np.argwhere(
                    np.array(box.parameters) == pt_item)[0]
                index_del.append(param_index)
                item_del.append(pt_item)

            else:
                break

        for item in pt_param:
            if item in box.parameters and item not in item_del:
                param_index = np.argwhere(np.array(box.parameters) == item)[0]
                index_del.append(param_index)
                item_del.append(item)

        samples = np.delete(samples, index_del, axis=1)
        ndim -= len(index_del)

        for item in item_del:
            box.parameters.remove(item)

    if box.spectrum == "petitradtrans" and box.attributes[
            "chemistry"] == "free":
        box.parameters.append("c_h_ratio")
        box.parameters.append("o_h_ratio")
        box.parameters.append("c_o_ratio")

        ndim += 3

        abund_index = {}
        for i, item in enumerate(box.parameters):
            if item == "CH4":
                abund_index["CH4"] = i

            elif item == "CO":
                abund_index["CO"] = i

            elif item == "CO_all_iso":
                abund_index["CO_all_iso"] = i

            elif item == "CO_all_iso_HITEMP":
                abund_index["CO_all_iso_HITEMP"] = i

            elif item == "CO2":
                abund_index["CO2"] = i

            elif item == "FeH":
                abund_index["FeH"] = i

            elif item == "H2O":
                abund_index["H2O"] = i

            elif item == "H2O_HITEMP":
                abund_index["H2O_HITEMP"] = i

            elif item == "H2S":
                abund_index["H2S"] = i

            elif item == "Na":
                abund_index["Na"] = i

            elif item == "NH3":
                abund_index["NH3"] = i

            elif item == "K":
                abund_index["K"] = i

            elif item == "PH3":
                abund_index["PH3"] = i

            elif item == "TiO":
                abund_index["TiO"] = i

            elif item == "TiO_all_Exomol":
                abund_index["TiO_all_Exomol"] = i

            elif item == "VO":
                abund_index["VO"] = i

            elif item == "VO_Plez":
                abund_index["VO_Plez"] = i

        c_h_ratio = np.zeros(samples.shape[0])
        o_h_ratio = np.zeros(samples.shape[0])
        c_o_ratio = np.zeros(samples.shape[0])

        for i, item in enumerate(samples):
            abund = {}

            if "CH4" in box.parameters:
                abund["CH4"] = item[abund_index["CH4"]]

            if "CO" in box.parameters:
                abund["CO"] = item[abund_index["CO"]]

            if "CO_all_iso" in box.parameters:
                abund["CO_all_iso"] = item[abund_index["CO"]]

            if "CO_all_iso_HITEMP" in box.parameters:
                abund["CO_all_iso_HITEMP"] = item[
                    abund_index["CO_all_iso_HITEMP"]]

            if "CO2" in box.parameters:
                abund["CO2"] = item[abund_index["CO2"]]

            if "FeH" in box.parameters:
                abund["FeH"] = item[abund_index["FeH"]]

            if "H2O" in box.parameters:
                abund["H2O"] = item[abund_index["H2O"]]

            if "H2O_HITEMP" in box.parameters:
                abund["H2O_HITEMP"] = item[abund_index["H2O_HITEMP"]]

            if "H2S" in box.parameters:
                abund["H2S"] = item[abund_index["H2S"]]

            if "Na" in box.parameters:
                abund["Na"] = item[abund_index["Na"]]

            if "K" in box.parameters:
                abund["K"] = item[abund_index["K"]]

            if "NH3" in box.parameters:
                abund["NH3"] = item[abund_index["NH3"]]

            if "PH3" in box.parameters:
                abund["PH3"] = item[abund_index["PH3"]]

            if "TiO" in box.parameters:
                abund["TiO"] = item[abund_index["TiO"]]

            if "TiO_all_Exomol" in box.parameters:
                abund["TiO_all_Exomol"] = item[abund_index["TiO_all_Exomol"]]

            if "VO" in box.parameters:
                abund["VO"] = item[abund_index["VO"]]

            if "VO_Plez" in box.parameters:
                abund["VO_Plez"] = item[abund_index["VO_Plez"]]

            c_h_ratio[i], o_h_ratio[i], c_o_ratio[
                i] = retrieval_util.calc_metal_ratio(abund)

    if (vmr and box.spectrum == "petitradtrans"
            and box.attributes["chemistry"] == "free"):
        print("Changing mass fractions to number fractions...",
              end="",
              flush=True)

        # Get all available line species
        line_species = retrieval_util.get_line_species()

        # Get the atomic and molecular masses
        masses = retrieval_util.atomic_masses()

        # Create array for the updated samples
        updated_samples = np.zeros(samples.shape)

        for i, samples_item in enumerate(samples):
            # Initiate a dictionary for the log10 mass fraction of the metals
            log_x_abund = {}

            for param_item in box.parameters:
                if param_item in line_species:
                    # Get the index of the parameter
                    param_index = box.parameters.index(param_item)

                    # Store log10 mass fraction in the dictionary
                    log_x_abund[param_item] = samples_item[param_index]

            # Create a dictionary with all mass fractions, including H2 and He
            x_abund = retrieval_util.mass_fractions(log_x_abund)

            # Calculate the mean molecular weight from the input mass fractions
            mmw = retrieval_util.mean_molecular_weight(x_abund)

            for param_item in box.parameters:
                if param_item in line_species:
                    # Get the index of the parameter
                    param_index = box.parameters.index(param_item)

                    # Overwrite the sample with the log10 number fraction
                    samples_item[param_index] = np.log10(
                        10.0**samples_item[param_index] * mmw /
                        masses[param_item])

            # Store the updated sample to the array
            updated_samples[i, ] = samples_item

        # Overwrite the samples in the SamplesBox
        box.samples = updated_samples

        print(" [DONE]")

    print("Median sample:")
    for key, value in box.median_sample.items():
        print(f"   - {key} = {value:.2e}")

    if "gauss_mean" in box.parameters:
        param_index = np.argwhere(np.array(box.parameters) == "gauss_mean")[0]
        samples[:, param_index] *= 1e3  # (um) -> (nm)

    if "gauss_sigma" in box.parameters:
        param_index = np.argwhere(np.array(box.parameters) == "gauss_sigma")[0]
        samples[:, param_index] *= 1e3  # (um) -> (nm)

    if box.prob_sample is not None:
        par_val = tuple(box.prob_sample.values())

        print("Maximum posterior sample:")
        for key, value in box.prob_sample.items():
            print(f"   - {key} = {value:.2e}")

    for item in box.parameters:
        if item[0:11] == "wavelength_":
            param_index = box.parameters.index(item)

            # (um) -> (nm)
            box.samples[:, param_index] *= 1e3

    if output is None:
        print("Plotting the posterior...", end="", flush=True)
    else:
        print(f"Plotting the posterior: {output}...", end="", flush=True)

    if "H2O" in box.parameters or "H2O_HITEMP" in box.parameters:
        samples = np.column_stack((samples, c_h_ratio, o_h_ratio, c_o_ratio))

    if inc_luminosity:
        if "teff" in box.parameters and "radius" in box.parameters:
            teff_index = np.argwhere(np.array(box.parameters) == "teff")[0]
            radius_index = np.argwhere(np.array(box.parameters) == "radius")[0]

            lum_planet = (4.0 * np.pi *
                          (samples[..., radius_index] * constants.R_JUP)**2 *
                          constants.SIGMA_SB * samples[..., teff_index]**4.0 /
                          constants.L_SUN)

            if "disk_teff" in box.parameters and "disk_radius" in box.parameters:
                teff_index = np.argwhere(
                    np.array(box.parameters) == "disk_teff")[0]
                radius_index = np.argwhere(
                    np.array(box.parameters) == "disk_radius")[0]

                lum_disk = (4.0 * np.pi *
                            (samples[..., radius_index] * constants.R_JUP)**2 *
                            constants.SIGMA_SB *
                            samples[..., teff_index]**4.0 / constants.L_SUN)

                samples = np.append(samples,
                                    np.log10(lum_planet + lum_disk),
                                    axis=-1)
                box.parameters.append("luminosity")
                ndim += 1

                samples = np.append(samples, lum_disk / lum_planet, axis=-1)
                box.parameters.append("luminosity_disk_planet")
                ndim += 1

            else:
                samples = np.append(samples, np.log10(lum_planet), axis=-1)
                box.parameters.append("luminosity")
                ndim += 1

        elif "teff_0" in box.parameters and "radius_0" in box.parameters:
            luminosity = 0.0

            for i in range(100):
                teff_index = np.argwhere(
                    np.array(box.parameters) == f"teff_{i}")
                radius_index = np.argwhere(
                    np.array(box.parameters) == f"radius_{i}")

                if len(teff_index) > 0 and len(radius_index) > 0:
                    luminosity += (
                        4.0 * np.pi *
                        (samples[..., radius_index[0]] * constants.R_JUP)**2 *
                        constants.SIGMA_SB * samples[..., teff_index[0]]**4.0 /
                        constants.L_SUN)

                else:
                    break

            samples = np.append(samples, np.log10(luminosity), axis=-1)
            box.parameters.append("luminosity")
            ndim += 1

            # teff_index = np.argwhere(np.array(box.parameters) == 'teff_0')
            # radius_index = np.argwhere(np.array(box.parameters) == 'radius_0')
            #
            # luminosity_0 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN
            #
            # samples = np.append(samples, np.log10(luminosity_0), axis=-1)
            # box.parameters.append('luminosity_0')
            # ndim += 1
            #
            # teff_index = np.argwhere(np.array(box.parameters) == 'teff_1')
            # radius_index = np.argwhere(np.array(box.parameters) == 'radius_1')
            #
            # luminosity_1 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN
            #
            # samples = np.append(samples, np.log10(luminosity_1), axis=-1)
            # box.parameters.append('luminosity_1')
            # ndim += 1
            #
            # teff_index_0 = np.argwhere(np.array(box.parameters) == 'teff_0')
            # radius_index_0 = np.argwhere(np.array(box.parameters) == 'radius_0')
            #
            # teff_index_1 = np.argwhere(np.array(box.parameters) == 'teff_1')
            # radius_index_1 = np.argwhere(np.array(box.parameters) == 'radius_1')
            #
            # luminosity_0 = 4. * np.pi * (samples[..., radius_index_0[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index_0[0]]**4. / constants.L_SUN
            #
            # luminosity_1 = 4. * np.pi * (samples[..., radius_index_1[0]]*constants.R_JUP)**2 \
            #     * constants.SIGMA_SB * samples[..., teff_index_1[0]]**4. / constants.L_SUN
            #
            # samples = np.append(samples, np.log10(luminosity_0/luminosity_1), axis=-1)
            # box.parameters.append('luminosity_ratio')
            # ndim += 1

            # r_tmp = samples[..., radius_index_0[0]]*constants.R_JUP
            # lum_diff = (luminosity_1*constants.L_SUN-luminosity_0*constants.L_SUN)
            #
            # m_mdot = (3600.*24.*365.25)*lum_diff*r_tmp/constants.GRAVITY/constants.M_JUP**2
            #
            # samples = np.append(samples, m_mdot, axis=-1)
            # box.parameters.append('m_mdot')
            # ndim += 1

    if inc_mass:
        if "logg" in box.parameters and "radius" in box.parameters:
            logg_index = np.argwhere(np.array(box.parameters) == "logg")[0]
            radius_index = np.argwhere(np.array(box.parameters) == "radius")[0]

            mass_samples = read_util.get_mass(samples[..., logg_index],
                                              samples[..., radius_index])

            samples = np.append(samples, mass_samples, axis=-1)
            box.parameters.append("mass")
            ndim += 1

        else:
            warnings.warn(
                "Samples with the log(g) and radius are required for 'inc_mass=True'."
            )

    if inc_loglike:
        # Get ln(L) of the samples
        ln_prob = box.ln_prob[..., np.newaxis]

        # Normalized by the maximum ln(L)
        ln_prob -= np.amax(ln_prob)

        # Convert ln(L) to log10(L)
        log_prob = ln_prob * np.exp(1.0)

        # Convert log10(L) to L
        prob = 10.0**log_prob

        # Normalize to an integrated probability of 1
        prob /= np.sum(prob)

        samples = np.append(samples, np.log10(prob), axis=-1)
        box.parameters.append("log_prob")
        ndim += 1

    labels = plot_util.update_labels(box.parameters)

    # Check if parameter values were fixed

    index_sel = []
    index_del = []

    for i in range(ndim):
        if np.amin(samples[:, i]) == np.amax(samples[:, i]):
            index_del.append(i)
        else:
            index_sel.append(i)

    samples = samples[:, index_sel]

    for i in range(len(index_del) - 1, -1, -1):
        del labels[index_del[i]]

    ndim -= len(index_del)

    samples = samples.reshape((-1, ndim))

    if isinstance(title_fmt, list) and len(title_fmt) != ndim:
        raise ValueError(
            f"The number of items in the list of 'title_fmt' ({len(title_fmt)}) is "
            f"not equal to the number of dimensions of the samples ({ndim}).")

    hist_titles = []

    for i, item in enumerate(labels):
        unit_start = item.find("(")

        if unit_start == -1:
            param_label = item
            unit_label = None

        else:
            param_label = item[:unit_start]
            # Remove parenthesis from the units
            unit_label = item[unit_start + 1:-1]

        q_16, q_50, q_84 = corner.quantile(samples[:, i], [0.16, 0.5, 0.84])
        q_minus, q_plus = q_50 - q_16, q_84 - q_50

        if isinstance(title_fmt, str):
            fmt = "{{0:{0}}}".format(title_fmt).format

        elif isinstance(title_fmt, list):
            fmt = "{{0:{0}}}".format(title_fmt[i]).format

        best_fit = r"${{{0}}}_{{-{1}}}^{{+{2}}}$"
        best_fit = best_fit.format(fmt(q_50), fmt(q_minus), fmt(q_plus))

        if unit_label is None:
            hist_title = f"{param_label} = {best_fit}"

        else:
            hist_title = f"{param_label} = {best_fit} {unit_label}"

        hist_titles.append(hist_title)

    fig = corner.corner(
        samples,
        quantiles=[0.16, 0.5, 0.84],
        labels=labels,
        label_kwargs={"fontsize": 13},
        titles=hist_titles,
        show_titles=True,
        title_fmt=None,
        title_kwargs={"fontsize": 12},
    )

    axes = np.array(fig.axes).reshape((ndim, ndim))

    for i in range(ndim):
        for j in range(ndim):
            if i >= j:
                ax = axes[i, j]

                ax.xaxis.set_major_formatter(ScalarFormatter(useOffset=False))
                ax.yaxis.set_major_formatter(ScalarFormatter(useOffset=False))

                labelleft = j == 0 and i != 0
                labelbottom = i == ndim - 1

                ax.tick_params(
                    axis="both",
                    which="major",
                    colors="black",
                    labelcolor="black",
                    direction="in",
                    width=1,
                    length=5,
                    labelsize=12,
                    top=True,
                    bottom=True,
                    left=True,
                    right=True,
                    labelleft=labelleft,
                    labelbottom=labelbottom,
                    labelright=False,
                    labeltop=False,
                )

                ax.tick_params(
                    axis="both",
                    which="minor",
                    colors="black",
                    labelcolor="black",
                    direction="in",
                    width=1,
                    length=3,
                    labelsize=12,
                    top=True,
                    bottom=True,
                    left=True,
                    right=True,
                    labelleft=labelleft,
                    labelbottom=labelbottom,
                    labelright=False,
                    labeltop=False,
                )

                if limits is not None:
                    ax.set_xlim(limits[j])

                if max_prob:
                    ax.axvline(par_val[j], color="tomato")

                if i > j:
                    if max_prob:
                        ax.axhline(par_val[i], color="tomato")
                        ax.plot(par_val[j], par_val[i], "s", color="tomato")

                    if limits is not None:
                        ax.set_ylim(limits[i])

                if offset is not None:
                    ax.get_xaxis().set_label_coords(0.5, offset[0])
                    ax.get_yaxis().set_label_coords(offset[1], 0.5)

                else:
                    ax.get_xaxis().set_label_coords(0.5, -0.26)
                    ax.get_yaxis().set_label_coords(-0.27, 0.5)

    if title:
        fig.suptitle(title, y=1.02, fontsize=16)

    print(" [DONE]")

    if output is None:
        plt.show()
    else:
        plt.savefig(output, bbox_inches="tight")

    plt.clf()
    plt.close()
Пример #27
0
    def run_mcmc(self, nwalkers: int, nsteps: int,
                 guess: Union[Dict[str, float], Dict[str,
                                                     None]], tag: str) -> None:
        """
        Function to run the MCMC sampler.

        Parameters
        ----------
        nwalkers : int
            Number of walkers.
        nsteps : int
            Number of steps per walker.
        guess : dict(str, float), dict(str, None)
            Guess of the scaling parameter.
        tag : str
            Database tag where the MCMC samples will be stored.

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

        print('Running MCMC...')

        ndim = 1

        initial = np.zeros((nwalkers, ndim))

        for i, item in enumerate(self.modelpar):
            if guess[item] is not None:
                width = min(abs(guess[item] - self.bounds[item][0]),
                            abs(guess[item] - self.bounds[item][1]))

                initial[:, i] = guess[item] + np.random.normal(
                    0, 0.1 * width, nwalkers)

            else:
                initial[:, i] = np.random.uniform(low=self.bounds[item][0],
                                                  high=self.bounds[item][1],
                                                  size=nwalkers)

        with Pool(processes=cpu_count()):
            ens_sampler = emcee.EnsembleSampler(nwalkers,
                                                ndim,
                                                lnprob,
                                                args=([
                                                    self.bounds, self.modelpar,
                                                    self.objphot, self.specphot
                                                ]))

            ens_sampler.run_mcmc(initial, nsteps, progress=True)

        species_db = database.Database()

        species_db.add_samples(sampler='emcee',
                               samples=ens_sampler.chain,
                               ln_prob=ens_sampler.lnprobability,
                               mean_accept=np.mean(
                                   ens_sampler.acceptance_fraction),
                               spectrum=('calibration', self.spectrum),
                               tag=tag,
                               modelpar=self.modelpar,
                               distance=None,
                               spec_labels=None)
Пример #28
0
def plot_mag_posterior(tag: str,
                       filter_name: str,
                       burnin: int = None,
                       xlim: Tuple[float, float] = None,
                       output: str = 'mag_posterior.pdf') -> None:
    """
    Function to plot the posterior distribution of the synthetic magnitudes.

    Parameters
    ----------
    tag : str
        Database tag with the posterior samples.
    filter_name : str
        Filter name.
    burnin : int, None
        Number of burnin steps to exclude. All samples are used if set to ``None``.
    xlim : tuple(float, float), None
        Axis limits. Automatically set if set to ``None``.
    output : str
        Output filename.

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

    mpl.rcParams['font.serif'] = ['Bitstream Vera Serif']
    mpl.rcParams['font.family'] = 'serif'

    plt.rc('axes', edgecolor='black', linewidth=2.2)

    species_db = database.Database()

    samples = species_db.get_mcmc_photometry(tag, filter_name, burnin)

    print(f'Plotting photometry samples: {output}...', end='', flush=True)

    fig = corner.corner(samples,
                        labels=['Magnitude'],
                        quantiles=[0.16, 0.5, 0.84],
                        label_kwargs={'fontsize': 13.},
                        show_titles=True,
                        title_kwargs={'fontsize': 12.},
                        title_fmt='.2f')

    axes = np.array(fig.axes).reshape((1, 1))

    ax = axes[0, 0]

    ax.tick_params(axis='both',
                   which='major',
                   colors='black',
                   labelcolor='black',
                   direction='in',
                   width=1,
                   length=5,
                   labelsize=12,
                   top=True,
                   bottom=True,
                   left=True,
                   right=True)

    ax.tick_params(axis='both',
                   which='minor',
                   colors='black',
                   labelcolor='black',
                   direction='in',
                   width=1,
                   length=3,
                   labelsize=12,
                   top=True,
                   bottom=True,
                   left=True,
                   right=True)

    if xlim is not None:
        ax.set_xlim(xlim)

    ax.get_xaxis().set_label_coords(0.5, -0.26)

    plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight')
    plt.clf()
    plt.close()

    print(' [DONE]')
Пример #29
0
def plot_walkers(
    tag: str,
    nsteps: Optional[int] = None,
    offset: Optional[Tuple[float, float]] = None,
    output: Optional[str] = "walkers.pdf",
) -> None:
    """
    Function to plot the step history of the walkers.

    Parameters
    ----------
    tag : str
        Database tag with the samples.
    nsteps : int, None
        Number of steps that are plotted. All steps are plotted if set to ``None``.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label. Default values are used if set to ``None``.
    output : str
        Output filename for the plot. The plot is shown in an
        interface window if the argument is set to ``None``.

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

    if output is None:
        print("Plotting walkers...", end="", flush=True)
    else:
        print(f"Plotting walkers: {output}...", end="", flush=True)

    mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"]
    mpl.rcParams["font.family"] = "serif"

    plt.rc("axes", edgecolor="black", linewidth=2.2)

    species_db = database.Database()
    box = species_db.get_samples(tag)

    samples = box.samples
    labels = plot_util.update_labels(box.parameters)

    if samples.ndim == 2:
        raise ValueError(
            f"The samples of '{tag}' have only 2 dimensions whereas 3 are required "
            f"for plotting the walkers. The plot_walkers function can only be "
            f"used after running the MCMC with run_mcmc and not after running "
            f"run_ultranest or run_multinest.")

    ndim = samples.shape[-1]

    plt.figure(1, figsize=(6, ndim * 1.5))
    gridsp = mpl.gridspec.GridSpec(ndim, 1)
    gridsp.update(wspace=0, hspace=0.1, left=0, right=1, bottom=0, top=1)

    for i in range(ndim):
        ax = plt.subplot(gridsp[i, 0])

        if i == ndim - 1:
            ax.tick_params(
                axis="both",
                which="major",
                colors="black",
                labelcolor="black",
                direction="in",
                width=1,
                length=5,
                labelsize=12,
                top=True,
                bottom=True,
                left=True,
                right=True,
                labelbottom=True,
            )

            ax.tick_params(
                axis="both",
                which="minor",
                colors="black",
                labelcolor="black",
                direction="in",
                width=1,
                length=3,
                labelsize=12,
                top=True,
                bottom=True,
                left=True,
                right=True,
                labelbottom=True,
            )

        else:
            ax.tick_params(
                axis="both",
                which="major",
                colors="black",
                labelcolor="black",
                direction="in",
                width=1,
                length=5,
                labelsize=12,
                top=True,
                bottom=True,
                left=True,
                right=True,
                labelbottom=False,
            )

            ax.tick_params(
                axis="both",
                which="minor",
                colors="black",
                labelcolor="black",
                direction="in",
                width=1,
                length=3,
                labelsize=12,
                top=True,
                bottom=True,
                left=True,
                right=True,
                labelbottom=False,
            )

        if i == ndim - 1:
            ax.set_xlabel("Step number", fontsize=10)
        else:
            ax.set_xlabel("", fontsize=10)

        ax.set_ylabel(labels[i], fontsize=10)

        if offset is not None:
            ax.get_xaxis().set_label_coords(0.5, offset[0])
            ax.get_yaxis().set_label_coords(offset[1], 0.5)

        else:
            ax.get_xaxis().set_label_coords(0.5, -0.22)
            ax.get_yaxis().set_label_coords(-0.09, 0.5)

        if nsteps is not None:
            ax.set_xlim(0, nsteps)

        for j in range(samples.shape[0]):
            ax.plot(samples[j, :, i], ls="-", lw=0.5, color="black", alpha=0.5)

    print(" [DONE]")

    if output is None:
        plt.show()
    else:
        plt.savefig(output, bbox_inches="tight")

    plt.clf()
    plt.close()
Пример #30
0
    def spectral_type(
        self,
        tag: str,
        spec_library,
        wavel_range: Optional[Tuple[Optional[float], Optional[float]]] = None,
        sptypes: Optional[List[str]] = None,
        av_ext: Optional[Union[List[float], np.array]] = None,
        rad_vel: Optional[Union[List[float], np.array]] = None,
    ) -> None:
        """
        Method for finding the best fitting empirical spectra from a selected library by
        evaluating the goodness-of-fit statistic from Cushing et al. (2008).

        Parameters
        ----------
        tag : str
            Database tag where for each spectrum from the spectral library the best-fit parameters
            will be stored. So when testing a range of values for ``av_ext`` and ``rad_vel``, only
            the parameters that minimize the goodness-of-fit statistic will be stored.
        spec_library : str
            Name of the spectral library ('irtf', 'spex', 'kesseli+2017', 'bonnefoy+2014').
        wavel_range : tuple(float, float), None
            Wavelength range (um) that is used for the empirical comparison.
        sptypes : list(str), None
            List with spectral types to compare with. The list should only contains types, for
            example ``sptypes=['M', 'L']``. All available spectral types in the ``spec_library``
            are compared with if set to ``None``.
        av_ext : list(float), np.array, None
            List of A_V extinctions for which the goodness-of-fit statistic is tested. The
            extinction is calculated with the empirical relation from Cardelli et al. (1989).
        rad_vel : list(float), np.array, None
            List of radial velocities (km s-1) for which the goodness-of-fit statistic is tested.

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

        w_i = 1.0

        if av_ext is None:
            av_ext = [0.0]

        if rad_vel is None:
            rad_vel = [0.0]

        h5_file = h5py.File(self.database, "r")

        try:
            h5_file[f"spectra/{spec_library}"]

        except KeyError:
            h5_file.close()
            species_db = database.Database()
            species_db.add_spectra(spec_library)
            h5_file = h5py.File(self.database, "r")

        # Read object spectra and resolution

        obj_spec = []
        obj_res = []

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

        # Read inverted covariance matrix
        # obj_inv_cov = self.object.get_spectrum()[self.spec_name][2]

        # Create empty lists for results

        name_list = []
        spt_list = []
        gk_list = []
        ck_list = []
        av_list = []
        rv_list = []

        print_message = ""

        # Start looping over library spectra

        for i, item in enumerate(h5_file[f"spectra/{spec_library}"]):
            # Read spectrum spectral type from library
            dset = h5_file[f"spectra/{spec_library}/{item}"]

            if isinstance(dset.attrs["sptype"], str):
                item_sptype = dset.attrs["sptype"]
            else:
                # Use decode for backward compatibility
                item_sptype = dset.attrs["sptype"].decode("utf-8")

            if item_sptype == "None":
                continue

            if sptypes is None or item_sptype[0] in sptypes:
                # Convert HDF5 dataset into numpy array
                spectrum = np.asarray(dset)

                if wavel_range is not None:
                    # Select subset of the spectrum

                    if wavel_range[0] is None:
                        indices = np.where(
                            (spectrum[:, 0] < wavel_range[1]))[0]

                    elif wavel_range[1] is None:
                        indices = np.where(
                            (spectrum[:, 0] > wavel_range[0]))[0]

                    else:
                        indices = np.where((spectrum[:, 0] > wavel_range[0])
                                           & (spectrum[:,
                                                       0] < wavel_range[1]))[0]

                    if len(indices) == 0:
                        raise ValueError(
                            "The selected wavelength range does not cover any "
                            "wavelength points of the input spectrum. Please "
                            "use a broader range as argument of 'wavel_range'."
                        )

                    spectrum = spectrum[indices, ]

                empty_message = len(print_message) * " "
                print(f"\r{empty_message}", end="")

                print_message = f"Processing spectra... {item}"
                print(f"\r{print_message}", end="")

                # Loop over all values of A_V and RV that will be tested

                for av_item in av_ext:
                    for rv_item in rad_vel:
                        for j, spec_item in enumerate(obj_spec):
                            # Dust extinction
                            ism_ext = dust_util.ism_extinction(
                                av_item, 3.1, spectrum[:, 0])
                            flux_scaling = 10.0**(-0.4 * ism_ext)

                            # Shift wavelengths by RV
                            wavel_shifted = (spectrum[:, 0] + spectrum[:, 0] *
                                             1e3 * rv_item / constants.LIGHT)

                            # Smooth spectrum
                            flux_smooth = read_util.smooth_spectrum(
                                wavel_shifted,
                                spectrum[:, 1] * flux_scaling,
                                spec_res=obj_res[j],
                                force_smooth=True,
                            )

                            # Interpolate library spectrum to object wavelengths
                            interp_spec = interp1d(
                                spectrum[:, 0],
                                flux_smooth,
                                kind="linear",
                                fill_value="extrapolate",
                            )

                            indices = np.where(
                                (spec_item[:, 0] > np.amin(spectrum[:, 0]))
                                & (spec_item[:, 0] < np.amax(spectrum[:,
                                                                      0])))[0]

                            flux_resample = interp_spec(spec_item[indices, 0])

                            c_numer = (w_i * spec_item[indices, 1] *
                                       flux_resample /
                                       spec_item[indices, 2]**2)

                            c_denom = (w_i * flux_resample**2 /
                                       spec_item[indices, 2]**2)

                            if j == 0:
                                g_k = 0.0
                                c_k_spec = []

                            c_k = np.sum(c_numer) / np.sum(c_denom)
                            c_k_spec.append(c_k)

                            chi_sq = (spec_item[indices, 1] - c_k *
                                      flux_resample) / spec_item[indices, 2]

                            g_k += np.sum(w_i * chi_sq**2)

                            # obj_inv_cov_crop = obj_inv_cov[indices, :]
                            # obj_inv_cov_crop = obj_inv_cov_crop[:, indices]

                            # g_k = np.dot(spec_item[indices, 1]-c_k*flux_resample,
                            #     np.dot(obj_inv_cov_crop,
                            #            spec_item[indices, 1]-c_k*flux_resample))

                        # Append to the lists of results

                        name_list.append(item)
                        spt_list.append(item_sptype)
                        gk_list.append(g_k)
                        ck_list.append(c_k_spec)
                        av_list.append(av_item)
                        rv_list.append(rv_item)

        empty_message = len(print_message) * " "
        print(f"\r{empty_message}", end="")

        print("\rProcessing spectra... [DONE]")

        h5_file.close()

        name_list = np.asarray(name_list)
        spt_list = np.asarray(spt_list)
        gk_list = np.asarray(gk_list)
        ck_list = np.asarray(ck_list)
        av_list = np.asarray(av_list)
        rv_list = np.asarray(rv_list)

        sort_index = np.argsort(gk_list)

        name_list = name_list[sort_index]
        spt_list = spt_list[sort_index]
        gk_list = gk_list[sort_index]
        ck_list = ck_list[sort_index]
        av_list = av_list[sort_index]
        rv_list = rv_list[sort_index]

        name_select = []
        spt_select = []
        gk_select = []
        ck_select = []
        av_select = []
        rv_select = []

        for i, item in enumerate(name_list):
            if item not in name_select:
                name_select.append(item)
                spt_select.append(spt_list[i])
                gk_select.append(gk_list[i])
                ck_select.append(ck_list[i])
                av_select.append(av_list[i])
                rv_select.append(rv_list[i])

        print("Best-fitting spectra:")

        if len(gk_select) < 10:
            for i, gk_item in enumerate(gk_select):
                print(
                    f"   {i+1:2d}. G = {gk_item:.2e} -> {name_select[i]}, {spt_select[i]}, "
                    f"A_V = {av_select[i]:.2f}, RV = {rv_select[i]:.0f} km/s,\n"
                    f"                      scalings = {ck_select[i]}")

        else:
            for i in range(10):
                print(
                    f"   {i+1:2d}. G = {gk_select[i]:.2e} -> {name_select[i]}, {spt_select[i]}, "
                    f"A_V = {av_select[i]:.2f}, RV = {rv_select[i]:.0f} km/s,\n"
                    f"                      scalings = {ck_select[i]}")

        species_db = database.Database()

        species_db.add_empirical(
            tag=tag,
            names=name_select,
            sptypes=spt_select,
            goodness_of_fit=gk_select,
            flux_scaling=ck_select,
            av_ext=av_select,
            rad_vel=rv_select,
            object_name=self.object_name,
            spec_name=self.spec_name,
            spec_library=spec_library,
        )