Exemple #1
0
    def get_spectrum(self,
                     model_param: Dict[str, Union[float, List[float]]],
                     spec_res: float,
                     smooth: bool = False) -> box.ModelBox:
        """
        Function for calculating a Planck spectrum or a combination of multiple Planck spectra.

        Parameters
        ----------
        model_param : dict
            Dictionary with the 'teff' (K), 'radius' (Rjup), and 'distance' (pc). The values
            of 'teff' and 'radius' can be a single float, or a list with floats for a combination
            of multiple Planck functions, e.g.
            {'teff': [1500., 1000.], 'radius': [1., 2.], 'distance': 10.}.
        spec_res : float
            Spectral resolution.
        smooth : bool

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

        if 'teff' in model_param and isinstance(model_param['teff'], list):
            model_param = self.update_parameters(model_param)

        wavel_points = read_util.create_wavelengths(self.wavel_range, spec_res)

        n_planck = 0

        for item in model_param:
            if item[:4] == 'teff':
                n_planck += 1

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

            else:
                scaling = 1.

            flux = self.planck(wavel_points,
                               model_param['teff'],
                               scaling)  # (W m-2 um-1)

        else:
            flux = np.zeros(wavel_points.shape)

            for i in range(n_planck):
                if f'radius_{i}' in model_param and 'distance' in model_param:
                    scaling = ((model_param[f'radius_{i}']*constants.R_JUP) /
                               (model_param['distance']*constants.PARSEC))**2

                else:
                    scaling = 1.

                flux += self.planck(wavel_points,
                                    model_param[f'teff_{i}'],
                                    scaling)  # (W m-2 um-1)

        if smooth:
            flux = read_util.smooth_spectrum(wavel_points, flux, spec_res)

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

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

        elif n_planck > 1:
            lum_total = 0.

            for i in range(n_planck):
                if f'radius_{i}' in model_box.parameters:
                    # Add up the luminosity of the blackbody components (Lsun)
                    surface = 4. * np.pi * (model_box.parameters[f'radius_{i}']*constants.R_JUP)**2

                    lum_total += surface * constants.SIGMA_SB * \
                        model_box.parameters[f'teff_{i}']**4. / constants.L_SUN

            if lum_total > 0.:
                model_box.parameters['luminosity'] = lum_total

        return model_box
Exemple #2
0
def add_btsettl(input_path: str, database: h5py._hl.files.File,
                wavel_range: Optional[Tuple[float, float]],
                teff_range: Optional[Tuple[float, float]],
                spec_res: Optional[float]) -> None:
    """
    Function for adding the BT-Settl atmospheric models (solar metallicity) to the database.
    The spectra had been downloaded from the Theoretical spectra web server
    (http://svo2.cab.inta-csic.es/svo/theory/newov2/index.php?models=bt-settl) and resampled
    to a spectral resolution of 5000 from 0.1 to 100 um.

    Parameters
    ----------
    input_path : str
        Folder where the data is located.
    database : h5py._hl.files.File
        Database.
    wavel_range : tuple(float, float), None
        Wavelength range (um). The original wavelength points are used if set to ``None``.
    teff_range : tuple(float, float), None
        Effective temperature range (K). All temperatures are selected if set to ``None``.
    spec_res : float, None
        Spectral resolution. Not used if ``wavel_range`` is set to ``None``.

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

    if not os.path.exists(input_path):
        os.makedirs(input_path)

    input_file = 'bt-settl.tgz'

    data_folder = os.path.join(input_path, 'bt-settl/')
    data_file = os.path.join(input_path, input_file)

    if not os.path.exists(data_folder):
        os.makedirs(data_folder)

    url = 'https://people.phys.ethz.ch/~ipa/tstolker/bt-settl.tgz'

    if not os.path.isfile(data_file):
        print('Downloading Bt-Settl model spectra (130 MB)...',
              end='',
              flush=True)
        urllib.request.urlretrieve(url, data_file)
        print(' [DONE]')

    print('Unpacking BT-Settl model spectra (130 MB)...', end='', flush=True)
    tar = tarfile.open(data_file)
    tar.extractall(data_folder)
    tar.close()
    print(' [DONE]')

    teff = []
    logg = []
    flux = []

    if wavel_range is not None and spec_res is not None:
        wavelength = read_util.create_wavelengths(wavel_range, spec_res)
    else:
        wavelength = None

    for _, _, file_list in os.walk(data_folder):
        for filename in sorted(file_list):
            if filename[:9] == 'bt-settl_':
                file_split = filename.split('_')

                teff_val = float(file_split[2])
                logg_val = float(file_split[4])

                if teff_range is not None:
                    if teff_val < teff_range[0] or teff_val > teff_range[1]:
                        continue

                print_message = f'Adding BT-Settl model spectra... {filename}'
                print(f'\r{print_message:<69}', end='')

                data_wavel, data_flux = np.loadtxt(os.path.join(
                    data_folder, filename),
                                                   unpack=True)

                teff.append(teff_val)
                logg.append(logg_val)

                if wavel_range is None or spec_res is None:
                    if wavelength is None:
                        wavelength = np.copy(data_wavel)  # (um)

                    if np.all(np.diff(wavelength) < 0):
                        raise ValueError(
                            'The wavelengths are not all sorted by increasing value.'
                        )

                    flux.append(data_flux)  # (W m-2 um-1)

                else:
                    flux_resample = spectres.spectres(wavelength,
                                                      data_wavel,
                                                      data_flux,
                                                      spec_errs=None,
                                                      fill=np.nan,
                                                      verbose=False)

                    if np.isnan(np.sum(flux_resample)):
                        raise ValueError(
                            f'Resampling is only possible if the new wavelength '
                            f'range ({wavelength[0]} - {wavelength[-1]} um) falls '
                            f'sufficiently far within the wavelength range '
                            f'({data_wavel[0]} - {data_wavel[-1]} um) of the input '
                            f'spectra.')

                    flux.append(flux_resample)  # (W m-2 um-1)

    print_message = 'Adding BT-Settl model spectra... [DONE]'
    print(f'\r{print_message:<69}')

    data_sorted = data_util.sort_data(np.asarray(teff), np.asarray(logg), None,
                                      None, None, wavelength, np.asarray(flux))

    data_util.write_data('bt-settl', ['teff', 'logg'], database, data_sorted)
Exemple #3
0
def add_drift_phoenix(input_path: str,
                      database: h5py._hl.files.File,
                      wavel_range: Optional[Tuple[float, float]] = None,
                      teff_range: Optional[Tuple[float, float]] = None,
                      spec_res: float = None) -> None:
    """
    Function for adding the DRIFT-PHOENIX atmospheric models to the database. The original spectra
    were downloaded from http://svo2.cab.inta-csic.es/theory/newov2/index.php?models=drift and have
    been resampled to a spectral resolution of R = 2000 from 0.1 to 50 um.

    Parameters
    ----------
    input_path : str
        Folder where the data is located.
    database : h5py._hl.files.File
        Database.
    wavel_range : tuple(float, float), None
        Wavelength range (um). The full wavelength range (0.1-50 um) is stored if set to ``None``.
        Only used in combination with ``spec_res``.
    teff_range : tuple(float, float), None
        Effective temperature range (K). All available temperatures are stored if set to ``None``.
    spec_res : float, None
        Spectral resolution. The data is stored with the spectral resolution of the input spectra
        (R = 2000) if set to ``None``. Only used in combination with ``wavel_range``.

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

    if not os.path.exists(input_path):
        os.makedirs(input_path)

    input_file = 'drift-phoenix.tgz'
    url = 'https://people.phys.ethz.ch/~ipa/tstolker/drift-phoenix.tgz'

    data_folder = os.path.join(input_path, 'drift-phoenix/')
    data_file = os.path.join(input_path, input_file)

    if not os.path.exists(data_folder):
        os.makedirs(data_folder)

    if not os.path.isfile(data_file):
        print('Downloading DRIFT-PHOENIX model spectra (229 MB)...',
              end='',
              flush=True)
        urllib.request.urlretrieve(url, data_file)
        print(' [DONE]')

    print('Unpacking DRIFT-PHOENIX model spectra (229 MB)...',
          end='',
          flush=True)
    tar = tarfile.open(data_file)
    tar.extractall(data_folder)
    tar.close()
    print(' [DONE]')

    teff = []
    logg = []
    feh = []
    flux = []

    if wavel_range is not None and spec_res is not None:
        wavelength = read_util.create_wavelengths(wavel_range, spec_res)
    else:
        wavelength = None

    for _, _, files in os.walk(data_folder):
        for filename in files:
            if filename[:14] == 'drift-phoenix_':
                file_split = filename.split('_')

                teff_val = float(file_split[2])
                logg_val = float(file_split[4])
                feh_val = float(file_split[6])

                if teff_range is not None:
                    if teff_val < teff_range[0] or teff_val > teff_range[1]:
                        continue

                print_message = f'Adding DRIFT-PHOENIX model spectra... {filename}'
                print(f'\r{print_message:<88}', end='')

                data_wavel, data_flux = np.loadtxt(os.path.join(
                    data_folder, filename),
                                                   unpack=True)

                teff.append(teff_val)
                logg.append(logg_val)
                feh.append(feh_val)

                if wavel_range is None or spec_res is None:
                    if wavelength is None:
                        wavelength = np.copy(data_wavel)  # (um)

                    if np.all(np.diff(wavelength) < 0):
                        raise ValueError(
                            'The wavelengths are not all sorted by increasing value.'
                        )

                    flux.append(data_flux)  # (W m-2 um-1)

                else:
                    flux_resample = spectres.spectres(wavelength,
                                                      data_wavel,
                                                      data_flux,
                                                      spec_errs=None,
                                                      fill=np.nan,
                                                      verbose=False)

                    if np.isnan(np.sum(flux_resample)):
                        raise ValueError(
                            f'Resampling is only possible if the new wavelength '
                            f'range ({wavelength[0]} - {wavelength[-1]} um) falls '
                            f'sufficiently far within the wavelength range '
                            f'({data_wavel[0]} - {data_wavel[-1]} um) of the input '
                            f'spectra.')

                    flux.append(flux_resample)  # (W m-2 um-1)

    print_message = 'Adding DRIFT-PHOENIX model spectra... [DONE]'
    print(f'\r{print_message:<88}')

    data_sorted = data_util.sort_data(np.asarray(teff), np.asarray(logg),
                                      np.asarray(feh), None, None, wavelength,
                                      np.asarray(flux))

    data_util.write_data('drift-phoenix', ['teff', 'logg', 'feh'], database,
                         data_sorted)
Exemple #4
0
def add_ames_dusty(input_path: str,
                   database: h5py._hl.files.File,
                   wavel_range: Optional[Tuple[float, float]] = None,
                   teff_range: Optional[Tuple[float, float]] = None,
                   spec_res: float = None) -> None:
    """
    Function for adding the AMES-Dusty atmospheric models to the database. The original spectra
    have been resampled to a spectral resolution of R = 2000 from 0.5 to 40 um. Note that a few
    of the spectra contain NaNs due to their limited, original wavelength coverage.

    Parameters
    ----------
    input_path : str
        Folder where the data is located.
    database : h5py._hl.files.File
        Database.
    wavel_range : tuple(float, float), None
        Wavelength range (um). The full wavelength range (0.5-40 um) is stored if set to ``None``.
        Only used in combination with ``spec_res``.
    teff_range : tuple(float, float), None
        Effective temperature range (K). All available temperatures are stored if set to ``None``.
    spec_res : float, None
        Spectral resolution. The data is stored with the spectral resolution of the input spectra
        (R = 2000) if set to ``None``. Only used in combination with ``wavel_range``.

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

    if not os.path.exists(input_path):
        os.makedirs(input_path)

    input_file = 'ames-dusty.tgz'
    url = 'https://people.phys.ethz.ch/~ipa/tstolker/ames-dusty.tgz'

    data_folder = os.path.join(input_path, 'ames-dusty/')
    data_file = os.path.join(input_path, input_file)

    if not os.path.exists(data_folder):
        os.makedirs(data_folder)

    if not os.path.isfile(data_file):
        print('Downloading AMES-Dusty model spectra (59 MB)...', end='', flush=True)
        urllib.request.urlretrieve(url, data_file)
        print(' [DONE]')

    print('Unpacking AMES-Dusty model spectra (59 MB)...', end='', flush=True)
    tar = tarfile.open(data_file)
    tar.extractall(data_folder)
    tar.close()
    print(' [DONE]')

    teff = []
    logg = []
    flux = []

    if wavel_range is not None and spec_res is not None:
        wavelength = read_util.create_wavelengths(wavel_range, spec_res)
    else:
        wavelength = None

    for _, _, files in os.walk(data_folder):
        for filename in files:
            if filename[:11] == 'ames-dusty_':
                file_split = filename.split('_')

                teff_val = float(file_split[2])
                logg_val = float(file_split[4])

                if teff_range is not None:
                    if teff_val < teff_range[0] or teff_val > teff_range[1]:
                        continue

                print_message = f'Adding AMES-Dusty model spectra... {filename}'
                print(f'\r{print_message:<73}', end='')

                data_wavel, data_flux = np.loadtxt(os.path.join(data_folder, filename), unpack=True)

                teff.append(teff_val)
                logg.append(logg_val)

                if wavel_range is None or spec_res is None:
                    if wavelength is None:
                        wavelength = np.copy(data_wavel)  # (um)

                    if np.all(np.diff(wavelength) < 0):
                        raise ValueError('The wavelengths are not all sorted by increasing value.')

                    # if np.isnan(np.sum(data_flux)):
                        # Three of the files contain partially NaNs due to a more limited
                        # wavelength coverage in the original spectra (before using spectres)
                        # data_flux = np.full(data_wavel.shape[0], np.nan)

                    flux.append(data_flux)  # (W m-2 um-1)

                else:
                    flux_resample = spectres.spectres(wavelength,
                                                      data_wavel,
                                                      data_flux,
                                                      spec_errs=None,
                                                      fill=np.nan,
                                                      verbose=False)

                    if np.isnan(np.sum(flux_resample)):
                        raise ValueError(f'Resampling is only possible if the new wavelength '
                                         f'range ({wavelength[0]} - {wavelength[-1]} um) falls '
                                         f'sufficiently far within the wavelength range '
                                         f'({data_wavel[0]} - {data_wavel[-1]} um) of the input '
                                         f'spectra.')

                    flux.append(flux_resample)  # (W m-2 um-1)

    print_message = 'Adding AMES-Dusty model spectra... [DONE]'
    print(f'\r{print_message:<73}')

    data_sorted = data_util.sort_data(np.asarray(teff),
                                      np.asarray(logg),
                                      None,
                                      None,
                                      None,
                                      wavelength,
                                      np.asarray(flux))

    data_util.write_data('ames-dusty',
                         ['teff', 'logg'],
                         database,
                         data_sorted)
Exemple #5
0
def add_exo_rem(input_path: str,
                database: h5py._hl.files.File,
                wavel_range: Optional[Tuple[float, float]] = None,
                teff_range: Optional[Tuple[float, float]] = None,
                spec_res: Optional[float] = None) -> None:
    """
    Function for adding the Exo-REM atmospheric models to the database.

    Parameters
    ----------
    input_path : str
        Folder where the data is located.
    database : h5py._hl.files.File
        Database.
    wavel_range : tuple(float, float), None
        Wavelength range (um). The original wavelength points with a spectral resolution of 5000
        are used if set to ``None``.
    teff_range : tuple(float, float), None
        Effective temperature range (K). All temperatures are selected if set to ``None``.
    spec_res : float, None
        Spectral resolution. Not used if ``wavel_range`` is set to ``None``.

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

    if not os.path.exists(input_path):
        os.makedirs(input_path)

    input_file = 'exo-rem.tgz'
    url = 'https://people.phys.ethz.ch/~ipa/tstolker/exo-rem.tgz'

    data_folder = os.path.join(input_path, 'exo-rem/')
    data_file = os.path.join(data_folder, input_file)

    if not os.path.exists(data_folder):
        os.makedirs(data_folder)

    if not os.path.isfile(data_file):
        print('Downloading Exo-REM model spectra (790 MB)...',
              end='',
              flush=True)
        urllib.request.urlretrieve(url, data_file)
        print(' [DONE]')

    print('Unpacking Exo-REM model spectra (790 MB)...', end='', flush=True)
    tar = tarfile.open(data_file)
    tar.extractall(data_folder)
    tar.close()
    print(' [DONE]')

    teff = []
    logg = []
    feh = []
    co_ratio = []
    flux = []

    if wavel_range is not None and spec_res is not None:
        wavelength = read_util.create_wavelengths(wavel_range, spec_res)
    else:
        wavelength = None

    for _, _, files in os.walk(data_folder):
        for filename in files:
            if filename[:8] == 'exo-rem_':
                file_split = filename.split('_')

                teff_val = float(file_split[2])
                logg_val = float(file_split[4])
                feh_val = float(file_split[6])
                co_val = float(file_split[8])

                if logg_val == 5.:
                    continue

                if co_val in [0.8, 0.85]:
                    continue

                if teff_range is not None:
                    if teff_val < teff_range[0] or teff_val > teff_range[1]:
                        continue

                print_message = f'Adding Exo-REM model spectra... {filename}'
                print(f'\r{print_message:<84}', end='')

                data_wavel, data_flux = np.loadtxt(os.path.join(
                    data_folder, filename),
                                                   unpack=True)

                teff.append(teff_val)
                logg.append(logg_val)
                feh.append(feh_val)
                co_ratio.append(co_val)

                if wavel_range is None or spec_res is None:
                    if wavelength is None:
                        wavelength = np.copy(data_wavel)  # (um)

                    if np.all(np.diff(wavelength) < 0):
                        raise ValueError(
                            'The wavelengths are not all sorted by increasing value.'
                        )

                    flux.append(data_flux)  # (W m-2 um-1)

                else:
                    flux_resample = spectres.spectres(wavelength,
                                                      data_wavel,
                                                      data_flux,
                                                      spec_errs=None,
                                                      fill=np.nan,
                                                      verbose=False)

                    if np.isnan(np.sum(flux_resample)):
                        raise ValueError(
                            f'Resampling is only possible if the new wavelength '
                            f'range ({wavelength[0]} - {wavelength[-1]} um) falls '
                            f'sufficiently far within the wavelength range '
                            f'({data_wavel[0]} - {data_wavel[-1]} um) of the input '
                            f'spectra.')

                    flux.append(flux_resample)  # (W m-2 um-1)

    print_message = 'Adding Exo-REM model spectra... [DONE]'
    print(f'\r{print_message:<84}')

    print('Grid points with the following parameters have been excluded:')
    print('   - log(g) = 5')
    print('   - C/O = 0.8')
    print('   - C/O = 0.85')

    data_sorted = data_util.sort_data(np.asarray(teff), np.asarray(logg),
                                      np.asarray(feh), np.asarray(co_ratio),
                                      None, wavelength, np.asarray(flux))

    data_util.write_data('exo-rem', ['teff', 'logg', 'feh', 'co'], database,
                         data_sorted)
Exemple #6
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
Exemple #7
0
    def get_spectrum(
        self,
        model_param: Optional[Dict[str, float]] = None,
        apply_mask: bool = False,
        spec_res: Optional[float] = None,
        extrapolate: bool = False,
        min_wavelength: Optional[float] = None,
    ) -> box.SpectrumBox:
        """
        Function for selecting the calibration spectrum.

        Parameters
        ----------
        model_param : dict, None
            Model parameters. Should contain the 'scaling' value. Not
            used if set to ``None``.
        apply_mask : bool
            Exclude negative values and NaN values.
        spec_res : float, None
            Spectral resolution. Original wavelength points are used if
            set to ``None``.
        extrapolate : bool
            Extrapolate to 6 um by fitting a power law function.
        min_wavelength : float, None
            Minimum wavelength used for fitting the power law function.
            All data is used if set to ``None``.

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

        with h5py.File(self.database, "r") as h5_file:
            data = np.asarray(h5_file[f"spectra/calibration/{self.tag}"])

            wavelength = np.asarray(data[0, ])
            flux = np.asarray(data[1, ])
            error = np.asarray(data[2, ])

        if apply_mask:
            indices = np.where(flux > 0.0)[0]

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

        if model_param is not None:
            flux = model_param["scaling"] * flux
            error = model_param["scaling"] * error

        if self.wavel_range is None:
            wl_index = np.ones(wavelength.size, dtype=bool)
        else:
            wl_index = ((flux > 0.0)
                        & (wavelength > self.wavel_range[0])
                        & (wavelength < self.wavel_range[1]))

        count = np.count_nonzero(wl_index)

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

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

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

            wavelength = wavelength[wl_index]
            flux = flux[wl_index]
            error = error[wl_index]

        if extrapolate:

            def _power_law(wavelength, offset, scaling, power_index):
                return offset + scaling * wavelength**power_index

            if min_wavelength:
                indices = np.where(wavelength > min_wavelength)[0]
            else:
                indices = np.arange(0, wavelength.size, 1)

            popt, pcov = curve_fit(
                f=_power_law,
                xdata=wavelength[indices],
                ydata=flux[indices],
                p0=(0.0, np.mean(flux[indices]), -1.0),
                sigma=error[indices],
            )

            sigma = np.sqrt(np.diag(pcov))

            print("Fit result for f(x) = a + b*x^c:")
            print(f"a = {popt[0]} +/- {sigma[0]}")
            print(f"b = {popt[1]} +/- {sigma[1]}")
            print(f"c = {popt[2]} +/- {sigma[2]}")

            while wavelength[-1] <= 6.0:
                wl_add = wavelength[-1] + wavelength[-1] / 1000.0

                wavelength = np.append(wavelength, wl_add)
                flux = np.append(flux,
                                 _power_law(wl_add, popt[0], popt[1], popt[2]))
                error = np.append(error, 0.0)

        if spec_res is not None:
            wavelength_new = read_util.create_wavelengths(
                (wavelength[0], wavelength[-1]), spec_res)

            flux_new, error_new = spectres.spectres(
                wavelength_new,
                wavelength,
                flux,
                spec_errs=error,
                fill=0.0,
                verbose=True,
            )

            wavelength = wavelength_new
            flux = flux_new
            error = error_new

        return box.create_box(
            boxtype="spectrum",
            spectrum="calibration",
            wavelength=wavelength,
            flux=flux,
            error=error,
            name=self.tag,
        )
Exemple #8
0
def add_petitcode_hot_cloudy(input_path: str,
                             database: h5py._hl.files.File,
                             wavel_range: Optional[Tuple[float, float]] = None,
                             teff_range: Optional[Tuple[float, float]] = None,
                             spec_res: Optional[float] = 1000.) -> None:
    """
    Function for adding the petitCODE hot cloudy atmospheric models to the database.

    Parameters
    ----------
    input_path : str
        Folder where the data is located.
    database : h5py._hl.files.File
        Database.
    wavel_range : tuple(float, float), None
        Wavelength range (um). The original wavelength points are used if set to None.
    teff_range : tuple(float, float), None
        Effective temperature range (K). All temperatures are selected if set to None.
    spec_res : float, None
        Spectral resolution. Not used if ``wavel_range`` is set to None.

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

    if not os.path.exists(input_path):
        os.makedirs(input_path)

    data_folder = os.path.join(input_path, 'petitcode-hot-cloudy/')

    url = 'https://people.phys.ethz.ch/~ipa/tstolker/petitcode-hot-cloudy.tgz'

    data_file = os.path.join(input_path, 'petitcode-hot-cloudy.tgz')

    if not os.path.isfile(data_file):
        print('Downloading petitCODE hot cloudy model spectra (276 MB)...',
              end='',
              flush=True)
        urllib.request.urlretrieve(url, data_file)
        print(' [DONE]')

    print('Unpacking petitCODE hot cloudy model spectra (276 MB)...',
          end='',
          flush=True)
    tar = tarfile.open(data_file)
    tar.extractall(data_folder)
    tar.close()
    print(' [DONE]')

    teff = []
    logg = []
    feh = []
    co_ratio = []
    fsed = []
    flux = []

    if wavel_range is not None:
        wavelength = read_util.create_wavelengths(wavel_range, spec_res)
    else:
        wavelength = None

    for _, _, files in os.walk(data_folder):
        for filename in files:
            file_split = filename.split('_')

            teff_val = float(file_split[2])
            logg_val = float(file_split[4])
            feh_val = float(file_split[6])
            co_ratio_val = float(file_split[8])
            fsed_val = float(file_split[10])

            if teff_range is not None:
                if teff_val < teff_range[0] or teff_val > teff_range[1]:
                    continue

            print_message = f'Adding petitCODE hot cloudy model spectra... {filename}'
            print(f'\r{print_message:<111}', end='')

            data = np.loadtxt(os.path.join(data_folder, filename))

            teff.append(teff_val)
            logg.append(logg_val)
            feh.append(feh_val)
            co_ratio.append(co_ratio_val)
            fsed.append(fsed_val)

            if wavel_range is None:
                if wavelength is None:
                    # (cm) -> (um)
                    wavelength = data[:, 0] * 1e4

                if np.all(np.diff(wavelength) < 0):
                    raise ValueError(
                        'The wavelengths are not all sorted by increasing value.'
                    )

                # (erg s-1 cm-2 Hz-1) -> (W m-2 um-1)
                flux.append(data[:, 1] * 1e-9 * constants.LIGHT /
                            (wavelength * 1e-6)**2)

            else:
                # (cm) -> (um)
                data_wavel = data[:, 0] * 1e4

                # (erg s-1 cm-2 Hz-1) -> (W m-2 um-1)
                data_flux = data[:, 1] * 1e-9 * constants.LIGHT / (data_wavel *
                                                                   1e-6)**2

                try:
                    flux.append(
                        spectres.spectres(wavelength, data_wavel, data_flux))
                except ValueError:
                    flux.append(np.zeros(wavelength.shape[0]))

                    warnings.warn(
                        'The wavelength range should fall within the range of the '
                        'original wavelength sampling. Storing zeros instead.')

    print_message = 'Adding petitCODE hot cloudy model spectra... [DONE]'
    print(f'\r{print_message:<111}')

    data_sorted = data_util.sort_data(np.asarray(teff), np.asarray(logg),
                                      np.asarray(feh), np.asarray(co_ratio),
                                      np.asarray(fsed), wavelength,
                                      np.asarray(flux))

    data_util.write_data('petitcode-hot-cloudy',
                         ['teff', 'logg', 'feh', 'co', 'fsed'], database,
                         data_sorted)
Exemple #9
0
    def get_spectrum(
        self,
        model_param: Dict[str, Union[float, List[float]]],
        spec_res: float,
        smooth: bool = False,
        wavel_resample: Optional[np.ndarray] = None,
    ) -> box.ModelBox:
        """
        Function for calculating a Planck spectrum or a combination of
        multiple Planck spectra. The spectrum is calculated at
        :math:`R = 500`. Afterwards, an optional smoothing and
        wavelength resampling can be applied.

        Parameters
        ----------
        model_param : dict
            Dictionary with the 'teff' (K), 'radius' (Rjup), and
            'parallax' (mas) or 'distance' (pc). The values of
            'teff' and 'radius' can be a single float, or a list
            with floats for a combination of multiple Planck
            functions, e.g. {'teff': [1500., 1000.],
            'radius': [1., 2.], 'distance': 10.}.
        spec_res : float
            Spectral resolution that is used for smoothing the spectrum
            with a Gaussian kernel when ``smooth=True``.
        smooth : bool
            If ``True``, the spectrum is smoothed to the spectral
            resolution of ``spec_res``.
        wavel_resample : np.ndarray, None
            Wavelength points (um) to which the spectrum will be
            resampled. The resampling is applied after the optional
            smoothing to ``spec_res`` when ``smooth=True``.

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

        if "teff" in model_param and isinstance(model_param["teff"], list):
            model_param = self.update_parameters(model_param)

        wavel_points = read_util.create_wavelengths(self.wavel_range, 500.0)

        n_planck = 0

        for item in model_param:
            if item[:4] == "teff":
                n_planck += 1

        if n_planck == 1:
            if "radius" in model_param and "parallax" in model_param:
                scaling = (
                    (model_param["radius"] * constants.R_JUP)
                    / (1e3 * constants.PARSEC / model_param["parallax"])
                ) ** 2

            elif "radius" in model_param and "distance" in model_param:
                scaling = (
                    (model_param["radius"] * constants.R_JUP)
                    / (model_param["distance"] * constants.PARSEC)
                ) ** 2

            else:
                scaling = 1.0

            flux = self.planck(
                wavel_points, model_param["teff"], scaling
            )  # (W m-2 um-1)

        else:
            flux = np.zeros(wavel_points.shape)

            for i in range(n_planck):
                if f"radius_{i}" in model_param and "parallax" in model_param:
                    scaling = (
                        (model_param[f"radius_{i}"] * constants.R_JUP)
                        / (1e3 * constants.PARSEC / model_param["parallax"])
                    ) ** 2

                elif f"radius_{i}" in model_param and "distance" in model_param:
                    scaling = (
                        (model_param[f"radius_{i}"] * constants.R_JUP)
                        / (model_param["distance"] * constants.PARSEC)
                    ) ** 2

                else:
                    scaling = 1.0

                flux += self.planck(
                    wavel_points, model_param[f"teff_{i}"], scaling
                )  # (W m-2 um-1)

        if smooth:
            flux = read_util.smooth_spectrum(wavel_points, flux, spec_res)

        model_box = box.create_box(
            boxtype="model",
            model="planck",
            wavelength=wavel_points,
            flux=flux,
            parameters=model_param,
            quantity="flux",
        )

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

            model_box.wavelength = wavel_resample
            model_box.flux = flux

        if n_planck == 1 and "radius" in model_param:
            model_box.parameters["luminosity"] = (
                4.0
                * np.pi
                * (model_box.parameters["radius"] * constants.R_JUP) ** 2
                * constants.SIGMA_SB
                * model_box.parameters["teff"] ** 4.0
                / constants.L_SUN
            )  # (Lsun)

        elif n_planck > 1:
            lum_total = 0.0

            for i in range(n_planck):
                if f"radius_{i}" in model_box.parameters:
                    # Add up the luminosity of the blackbody components (Lsun)
                    surface = (
                        4.0
                        * np.pi
                        * (model_box.parameters[f"radius_{i}"] * constants.R_JUP) ** 2
                    )

                    lum_total += (
                        surface
                        * constants.SIGMA_SB
                        * model_box.parameters[f"teff_{i}"] ** 4.0
                        / constants.L_SUN
                    )

            if lum_total > 0.0:
                model_box.parameters["luminosity"] = lum_total

        return model_box
Exemple #10
0
    def integrate_flux(
        self,
        wavel_int: Tuple[float, float],
        interp_kind: str = "linear",
        plot_filename: Optional[str] = "int_line.pdf",
    ) -> Union[np.float64, np.float64]:
        """
        Method for calculating the integrated line flux and error. The
        spectrum is first interpolated to :math:`R = 100000` and then
        integrated across the specified wavelength range with the
        composite trapezoidal rule of ``np.trapz``. The error is
        estimated with a Monte Carlo approach from 1000 samples.

        Parameters
        ----------
        wavel_int : tuple(float, float)
            Wavelength range (um) across which the flux
            will be integrated.
        interp_kind : str
            Kind of interpolation kind for
            ``scipy.interpolate.interp1d`` (default: 'linear').
        plot_filename : str, None
            Filename for the plot with the interpolated line profile.
            The plot is shown in an interface window if the argument
            is set to ``None``.

        Returns
        -------
        float
            Integrated line flux (W m-2).
        float
            Flux error (W m-2).
        """

        if plot_filename is None:
            print("Plotting integrated line...", end="", flush=True)
        else:
            print(f"Plotting integrated line: {plot_filename}...", end="", flush=True)

        n_samples = 1000

        wavel_high_res = read_util.create_wavelengths(wavel_int, 1e5)

        # Creating plot

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

        plt.rc("axes", edgecolor="black", linewidth=2)
        plt.rcParams["axes.axisbelow"] = False

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

        ax1 = plt.subplot(gs[0, 0])
        ax2 = ax1.twiny()

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

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

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

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

        ax1.set_xlabel("Wavelength (µm)", fontsize=16)
        ax1.set_ylabel("Flux (W m$^{-2}$ µm$^{-1}$)", fontsize=16)
        ax2.set_xlabel("Velocity (km s$^{-1}$)", fontsize=16)

        ax1.get_xaxis().set_label_coords(0.5, -0.12)
        ax1.get_yaxis().set_label_coords(-0.1, 0.5)
        ax2.get_xaxis().set_label_coords(0.5, 1.12)

        ax1.plot(
            self.spectrum[:, 0],
            self.spectrum[:, 1],
            color="black",
            label=self.spec_name,
        )
        ax2.plot(self.spec_vrad, self.spectrum[:, 1], ls="-", lw=0.0)

        flux_sample = np.zeros(n_samples)
        fwhm_sample = np.zeros(n_samples)
        mean_sample = np.zeros(n_samples)
        vrad_sample = np.zeros(n_samples)
        lum_sample = np.zeros(n_samples)

        for i in range(n_samples):
            # Sample fluxes from random errors
            spec_rand = np.random.normal(self.spectrum[:, 1], self.spectrum[:, 2])

            # Interpolate sampled spectrum
            spec_interp = interp1d(
                self.spectrum[:, 0], spec_rand, kind=interp_kind, bounds_error=False
            )

            # Resample to high-resolution wavelengths
            flux_rand = spec_interp(wavel_high_res)

            # Integrate line flux (W m-2)
            flux_sample[i] = np.trapz(flux_rand, wavel_high_res)

            # Line luminosity (Lsun)
            lum_sample[i] = (
                4.0 * np.pi * (1e3 * constants.PARSEC / self.parallax) ** 2 * flux_sample[i]
            )
            lum_sample[i] /= constants.L_SUN  # (Lsun)

            # Weighted (with flux) mean wavelength (um)
            mean_sample[i] = np.trapz(
                wavel_high_res * flux_rand, wavel_high_res
            ) / np.trapz(flux_rand, wavel_high_res)

            # Radial velocity (km s-1)
            vrad_sample[i] = (
                1e-3
                * constants.LIGHT
                * (mean_sample[i] - self.lambda_rest)
                / self.lambda_rest
            )

            # Find full width at half maximum

            spline = InterpolatedUnivariateSpline(
                wavel_high_res, flux_rand - np.max(flux_rand) / 2.0
            )
            root = spline.roots()

            diff = root - mean_sample[i]

            root1 = np.amax(diff[diff < 0.0])
            root2 = np.amin(diff[diff > 0.0])

            fwhm_sample[i] = 1e-3 * constants.LIGHT * (root2 - root1) / mean_sample[i]

            # Add 30 samples to the plot

            if i == 0:
                ax1.plot(
                    wavel_high_res,
                    flux_rand,
                    ls="-",
                    lw=0.5,
                    color="gray",
                    alpha=0.4,
                    label="Random samples",
                )

            elif i < 30:
                ax1.plot(
                    wavel_high_res, flux_rand, ls="-", lw=0.5, color="gray", alpha=0.4
                )

        # Line flux from original, interpolated spectrum

        spec_interp = interp1d(
            self.spectrum[:, 0],
            self.spectrum[:, 1],
            kind=interp_kind,
            bounds_error=False,
        )

        flux_high_res = spec_interp(wavel_high_res)

        line_flux = np.trapz(flux_high_res, wavel_high_res)

        ax1.plot(
            wavel_high_res, flux_high_res, color="tab:blue", label="High resolution"
        )

        ax1.legend(loc="upper right", frameon=False, fontsize=12.0)

        print(" [DONE]")

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

        plt.clf()
        plt.close()

        wavel_mean, wavel_std = np.mean(mean_sample), np.std(mean_sample)
        print(f"Mean wavelength (nm): {1e3*wavel_mean:.2f} +/- {1e3*wavel_std:.2f}")

        fwhm_mean, fwhm_std = np.mean(fwhm_sample), np.std(fwhm_sample)
        print(f"FWHM (km s-1): {fwhm_mean:.2f} +/- {fwhm_std:.2f}")

        vrad_mean, vrad_std = np.mean(vrad_sample), np.std(vrad_sample)
        print(f"Radial velocity (km s-1): {vrad_mean:.1f} +/- {vrad_std:.1f}")

        line_error = np.std(flux_sample)
        print(f"Line flux (W m-2): {line_flux:.2e} +/- {line_error:.2e}")

        lum_mean, lum_std = np.mean(lum_sample), np.std(lum_sample)
        print(f"Line luminosity (Lsun): {lum_mean:.2e} +/- {lum_std:.2e}")

        return line_flux, line_error
Exemple #11
0
def add_petitcode_hot_clear(input_path: str,
                            database: h5py._hl.files.File,
                            data_folder: str,
                            wavel_range: Optional[Tuple[float, float]] = None,
                            teff_range: Optional[Tuple[float, float]] = None,
                            spec_res: Optional[float] = 1000.) -> None:
    """
    Function for adding the petitCODE hot clear atmospheric models to the database.

    Parameters
    ----------
    input_path : str
        Folder where the data is located.
    database : h5py._hl.files.File
        Database.
    data_folder : str
        Path with input data.
    wavel_range : tuple(float, float), None
        Wavelength range (um). The original wavelength points are used if set to None.
    teff_range : tuple(float, float), None
        Effective temperature range (K). All temperatures are selected if set to None.
    spec_res : float, None
        Spectral resolution. Not used if ``wavel_range`` is set to None.

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

    if not os.path.exists(input_path):
        os.makedirs(input_path)

    teff = []
    logg = []
    feh = []
    co_ratio = []
    flux = []

    if wavel_range is not None:
        wavelength = read_util.create_wavelengths(wavel_range, spec_res)
    else:
        wavelength = None

    for _, _, files in os.walk(data_folder):
        for filename in files:
            teff_val = float(filename[9:13])
            logg_val = float(filename[19:23])
            feh_val = float(filename[28:32])
            co_ratio_val = float(filename[36:40])

            if teff_range is not None:
                if teff_val < teff_range[0] or teff_val > teff_range[1]:
                    continue

            print_message = f'Adding petitCODE hot clear model spectra... {filename}'
            print(f'\r{print_message:<100}', end='')

            data = np.loadtxt(os.path.join(data_folder, filename))

            teff.append(teff_val)
            logg.append(logg_val)
            feh.append(feh_val)
            co_ratio.append(co_ratio_val)

            if wavel_range is None:
                if wavelength is None:
                    # (cm) -> (um)
                    wavelength = data[:, 0] * 1e4

                if np.all(np.diff(wavelength) < 0):
                    raise ValueError(
                        'The wavelengths are not all sorted by increasing value.'
                    )

                # (erg s-1 cm-2 Hz-1) -> (W m-2 um-1)
                flux.append(data[:, 1] * 1e-9 * constants.LIGHT /
                            (wavelength * 1e-6)**2)

            else:
                # (cm) -> (um)
                data_wavel = data[:, 0] * 1e4

                # (erg s-1 cm-2 Hz-1) -> (W m-2 um-1)
                data_flux = data[:, 1] * 1e-9 * constants.LIGHT / (data_wavel *
                                                                   1e-6)**2

                try:
                    flux.append(
                        spectres.spectres(wavelength, data_wavel, data_flux))
                except ValueError:
                    flux.append(np.zeros(wavelength.shape[0]))

                    warnings.warn(
                        'The wavelength range should fall within the range of the '
                        'original wavelength sampling. Storing zeros instead.')

    print_message = 'Adding petitCODE hot clear model spectra... [DONE]'
    print(f'\r{print_message:<100}')

    data_sorted = data_util.sort_data(np.asarray(teff), np.asarray(logg),
                                      np.asarray(feh), np.asarray(co_ratio),
                                      None, wavelength, np.asarray(flux))

    data_util.write_data('petitcode-hot-clear', ['teff', 'logg', 'feh', 'co'],
                         database, data_sorted)
Exemple #12
0
def add_model_grid(
    model_name: str,
    input_path: str,
    database: h5py._hl.files.File,
    wavel_range: Optional[Tuple[float, float]],
    teff_range: Optional[Tuple[float, float]],
    spec_res: Optional[float],
) -> None:
    """
    Function for adding a grid of model spectra to the database.
    The original spectra had been resampled to logarithmically-
    spaced wavelengths, so at a constant resolution,
    :math:`\\lambda/\\Delta\\lambda`. This function downloads
    the model grid, unpacks the tar file, and adds the spectra
    and parameters to the database.

    Parameters
    ----------
    model_name : str
        Name of the model grid.
    input_path : str
        Folder where the data is located.
    database : h5py._hl.files.File
        Database.
    wavel_range : tuple(float, float), None
        Wavelength range (um). The original wavelength
        points are used if set to ``None``.
    teff_range : tuple(float, float), None
        Effective temperature range (K). All temperatures
        are selected if set to ``None``.
    spec_res : float, None
        Spectral resolution for resampling. Not used if
        ``wavel_range`` is set to ``None`` and/or
        ``spec_res`` is set to ``None``

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

    data_file = pathlib.Path(__file__).parent.resolve() / "model_data.json"

    with open(data_file, "r", encoding="utf-8") as json_file:
        model_data = json.load(json_file)

    if model_name in model_data.keys():
        model_info = model_data[model_name]

    else:
        raise ValueError(
            f"The {model_name} atmospheric model is not available. "
            f"Please choose one of the following models: "
            f"'ames-cond', 'ames-dusty', 'atmo', 'bt-settl', "
            f"'bt-nextgen', 'drift-phoexnix', 'petitcode-cool-clear', "
            f"'petitcode-cool-cloudy', 'petitcode-hot-clear', "
            f"'petitcode-hot-cloudy', 'exo-rem', 'bt-settl-cifist', "
            f"'bt-cond', 'bt-cond-feh', 'blackbody', 'sonora-cholla', "
            f"'sonora-bobcat', 'sonora-bobcat-co', 'koester-wd'")

    if model_name == "bt-settl":
        warnings.warn("It is recommended to use the CIFIST "
                      "grid of the BT-Settl, because it is "
                      "a newer version. In that case, set "
                      "model='bt-settl-cifist' when using "
                      "add_model of Database.")

    if not os.path.exists(input_path):
        os.makedirs(input_path)

    input_file = f"{model_name}.tgz"

    data_folder = os.path.join(input_path, model_name)
    data_file = os.path.join(input_path, input_file)

    if not os.path.exists(data_folder):
        os.makedirs(data_folder)

    url = f"https://home.strw.leidenuniv.nl/~stolker/species/{model_name}.tgz"

    if not os.path.isfile(data_file):
        print(
            f"Downloading {model_info['name']} model "
            f"spectra ({model_info['file size']})...",
            end="",
            flush=True,
        )
        urllib.request.urlretrieve(url, data_file)
        print(" [DONE]")

    print(
        f"Unpacking {model_info['name']} model "
        f"spectra ({model_info['file size']})...",
        end="",
        flush=True,
    )
    tar = tarfile.open(data_file)
    tar.extractall(data_folder)
    tar.close()
    print(" [DONE]")

    if "information" in model_info:
        print(f"Model information: {model_info['information']}")

    if "reference" in model_info:
        print(f"Please cite {model_info['reference']} when "
              f"using {model_info['name']} in a publication")

    if "url" in model_info:
        print(f"Reference URL: {model_info['url']}")

    teff = []

    if "logg" in model_info["parameters"]:
        logg = []
    else:
        logg = None

    if "feh" in model_info["parameters"]:
        feh = []
    else:
        feh = None

    if "c_o_ratio" in model_info["parameters"]:
        c_o_ratio = []
    else:
        c_o_ratio = None

    if "fsed" in model_info["parameters"]:
        fsed = []
    else:
        fsed = None

    if "log_kzz" in model_info["parameters"]:
        log_kzz = []
    else:
        log_kzz = None

    flux = []

    if wavel_range is not None and spec_res is not None:
        wavelength = read_util.create_wavelengths(wavel_range, spec_res)
        print(f"Wavelength range (um) = {wavel_range[0]} - {wavel_range[1]}")
        print(f"Spectral resolution = {spec_res}")

    else:
        wavelength = None
        print(f"Wavelength range (um) = "
              f"{model_info['wavelength range'][0]} - "
              f"{model_info['wavelength range'][1]}")
        print(f"Spectral resolution = {model_info['resolution']}")

    if teff_range is None:
        print(
            f"Teff range (K) = {model_info['teff range'][0]} - {model_info['teff range'][1]}"
        )
    else:
        print(f"Teff range (K) = {teff_range[0]} - {teff_range[1]}")

    print_message = ""

    for _, _, file_list in os.walk(data_folder):
        for filename in sorted(file_list):

            if filename[:len(model_name)] == model_name:
                file_split = filename.split("_")

                param_index = file_split.index("teff") + 1
                teff_val = float(file_split[param_index])

                if teff_range is not None:
                    if teff_val < teff_range[0] or teff_val > teff_range[1]:
                        continue

                teff.append(teff_val)

                if logg is not None:
                    param_index = file_split.index("logg") + 1
                    logg.append(float(file_split[param_index]))

                if feh is not None:
                    param_index = file_split.index("feh") + 1
                    feh.append(float(file_split[param_index]))

                if c_o_ratio is not None:
                    param_index = file_split.index("co") + 1
                    c_o_ratio.append(float(file_split[param_index]))

                if fsed is not None:
                    param_index = file_split.index("fsed") + 1
                    fsed.append(float(file_split[param_index]))

                if log_kzz is not None:
                    param_index = file_split.index("logkzz") + 1
                    log_kzz.append(float(file_split[param_index]))

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

                print_message = (
                    f"Adding {model_info['name']} model spectra... {filename}")
                print(f"\r{print_message}", end="")

                data_wavel, data_flux = np.loadtxt(os.path.join(
                    data_folder, filename),
                                                   unpack=True)

                if wavel_range is None or spec_res is None:
                    if wavelength is None:
                        wavelength = np.copy(data_wavel)  # (um)

                    if np.all(np.diff(wavelength) < 0):
                        raise ValueError(
                            "The wavelengths are not all sorted by increasing value."
                        )

                    flux.append(data_flux)  # (W m-2 um-1)

                else:
                    flux_resample = spectres.spectres(
                        wavelength,
                        data_wavel,
                        data_flux,
                        spec_errs=None,
                        fill=np.nan,
                        verbose=False,
                    )

                    if np.isnan(np.sum(flux_resample)):
                        raise ValueError(
                            f"Resampling is only possible if the new wavelength "
                            f"range ({wavelength[0]} - {wavelength[-1]} um) falls "
                            f"sufficiently far within the wavelength range "
                            f"({data_wavel[0]} - {data_wavel[-1]} um) of the input "
                            f"spectra.")

                    flux.append(flux_resample)  # (W m-2 um-1)

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

    print_message = f"Adding {model_info['name']} model spectra... [DONE]"
    print(f"\r{print_message}")

    if logg is not None:
        logg = np.asarray(logg)

    if feh is not None:
        feh = np.asarray(feh)

    if c_o_ratio is not None:
        c_o_ratio = np.asarray(c_o_ratio)

    if fsed is not None:
        fsed = np.asarray(fsed)

    if log_kzz is not None:
        log_kzz = np.asarray(log_kzz)

    data_sorted = data_util.sort_data(
        np.asarray(teff),
        logg,
        feh,
        c_o_ratio,
        fsed,
        log_kzz,
        wavelength,
        np.asarray(flux),
    )

    data_util.write_data(model_name, model_info["parameters"], database,
                         data_sorted)