示例#1
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"]
示例#2
0
    def __init__(
        self,
        object_name: str,
        spec_name: Union[str, List[str]],
        spec_library: Optional[str] = None,
    ) -> None:
        """
        Parameters
        ----------
        object_name : str
            Object name as stored in the database with
            :func:`~species.data.database.Database.add_object` or
            :func:`~species.data.database.Database.add_companion`.
        spec_name : str, list(str)
            Name of the spectrum or list with spectrum names that are stored at the object data
            of ``object_name``. The argument can be either a string or a list of strings.
        spec_library : str, None
            DEPRECATED: Name of the spectral library ('irtf', 'spex', or 'kesseli+2017).

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

        self.object_name = object_name
        self.spec_name = spec_name

        if isinstance(self.spec_name, str):
            self.spec_name = [self.spec_name]

        if spec_library is not None:
            warnings.warn(
                "The 'spec_library' parameter is no longer used by the constructor "
                "of CompareSpectra and will be removed in a future release.",
                DeprecationWarning,
            )

        self.object = read_object.ReadObject(object_name)

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

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

        self.database = config["species"]["database"]
示例#3
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']
示例#4
0
def plot_color_color(boxes: list,
                     objects: Optional[Union[
                         List[Tuple[str, Tuple[str, str], Tuple[str, str]]],
                         List[Tuple[str, Tuple[str, str], Tuple[str, str],
                                    Optional[dict], Optional[dict]]]]] = None,
                     mass_labels: Optional[Union[List[float],
                                                 List[Tuple[float,
                                                            str]]]] = None,
                     teff_labels: Optional[Union[List[float],
                                                 List[Tuple[float,
                                                            str]]]] = None,
                     companion_labels: bool = False,
                     reddening: Optional[List[Tuple[Tuple[str,
                                                          str], Tuple[str,
                                                                      str],
                                                    Tuple[str,
                                                          float], str, float,
                                                    Tuple[float,
                                                          float]]]] = None,
                     field_range: Optional[Tuple[str, str]] = None,
                     label_x: str = 'Color',
                     label_y: str = 'Color',
                     xlim: Optional[Tuple[float, float]] = None,
                     ylim: Optional[Tuple[float, float]] = None,
                     offset: Optional[Tuple[float, float]] = None,
                     legend: Optional[Union[str, dict,
                                            Tuple[float,
                                                  float]]] = 'upper left',
                     figsize: Optional[Tuple[float, float]] = (4., 4.3),
                     output: str = 'color-color.pdf') -> None:
    """
    Function for creating a color-color diagram.

    Parameters
    ----------
    boxes : list(species.core.box.ColorColorBox, species.core.box.IsochroneBox, )
        Boxes with the color-color and isochrone data from photometric libraries, spectral
        libraries, and/or atmospheric models. The synthetic data have to be created with
        :func:`~species.read.read_isochrone.ReadIsochrone.get_color_color`. These boxes
        contain synthetic colors for a given age and a range of masses.
    objects : tuple(tuple(str, tuple(str, str), tuple(str, str)), ),
              tuple(tuple(str, tuple(str, str), tuple(str, str), dict, dict), ), None
        Tuple with individual objects. The objects require a tuple with their database tag, the two
        filter names for the first color, and the two filter names for the second color.
        Optionally, a dictionary with keyword arguments can be provided for the object's marker and
        label, respectively. For example, ``{'marker': 'o', 'ms': 10}`` for the marker and
        ``{'ha': 'left', 'va': 'bottom', 'xytext': (5, 5)})`` for the label. Not used if set to
        None.
    mass_labels : list(float, ), list(tuple(float, str), ), None
        Plot labels with masses next to the isochrone data of `models`. The list with masses has
        to be provided in Jupiter mass. Alternatively, a list of tuples can be provided with
        the planet mass and position of the label ('left' or 'right), for example
        ``[(10., 'left'), (20., 'right')]``. No labels are shown if set to None.
    teff_labels : list(float, ), list(tuple(float, str), ), None
        Plot labels with temperatures (K) next to the synthetic Planck photometry. Alternatively,
        a list of tuples can be provided with the planet mass and position of the label ('left' or
        'right), for example ``[(1000., 'left'), (1200., 'right')]``. No labels are shown if set
        to None.
    companion_labels : bool
        Plot labels with the names of the directly imaged companions.
    reddening : list(tuple(tuple(str, str), tuple(str, str), tuple(str, float), str, float, tuple(float, float)), None
        Include reddening arrows by providing a list with tuples. Each tuple contains the filter
        names for the color, the filter name for the magnitude, the particle radius (um), and the
        start position (color, mag) of the arrow in the plot, so (filter_color_1, filter_color_2,
        filter_mag, composition, radius, (x_pos, y_pos)). The composition can be either 'Fe' or
        'MgSiO3' (both with crystalline structure). The parameter is not used if set to ``None``.
    field_range : tuple(str, str), None
        Range of the discrete colorbar for the field dwarfs. The tuple should contain the lower
        and upper value ('early M', 'late M', 'early L', 'late L', 'early T', 'late T', 'early Y).
        The full range is used if set to None.
    label_x : str
        Label for the x-axis.
    label_y : str
        Label for the y-axis.
    xlim : tuple(float, float)
        Limits for the x-axis.
    ylim : tuple(float, float)
        Limits for the y-axis.
    offset : tuple(float, float), None
        Offset of the x- and y-axis label.
    legend : str, tuple(float, float), dict, None
        Legend position or keyword arguments. No legend is shown if set to ``None``.
    figsize : tuple(float, float)
        Figure size.
    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)

    model_color = ('#234398', '#f6a432', 'black')
    model_linestyle = ('-', '--', ':', '-.')

    isochrones = []
    planck = []
    models = []
    empirical = []

    for item in boxes:
        if isinstance(item, box.IsochroneBox):
            isochrones.append(item)

        elif isinstance(item, box.ColorColorBox):
            if item.object_type == 'model':
                models.append(item)

            elif item.library == 'planck':
                planck.append(item)

            else:
                empirical.append(item)

        else:
            raise ValueError(
                f'Found a {type(item)} while only ColorColorBox and IsochroneBox '
                f'objects can be provided to \'boxes\'.')

    plt.figure(1, figsize=figsize)
    gridsp = mpl.gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.1, 4.])
    gridsp.update(wspace=0., hspace=0., left=0, right=1, bottom=0, top=1)

    ax1 = plt.subplot(gridsp[2, 0])
    ax2 = plt.subplot(gridsp[0, 0])

    ax1.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)

    ax1.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)

    ax1.xaxis.set_major_locator(MultipleLocator(0.5))
    ax1.yaxis.set_major_locator(MultipleLocator(0.5))

    ax1.xaxis.set_minor_locator(MultipleLocator(0.1))
    ax1.yaxis.set_minor_locator(MultipleLocator(0.1))

    ax1.set_xlabel(label_x, fontsize=14)
    ax1.set_ylabel(label_y, fontsize=14)

    ax1.invert_yaxis()

    if offset:
        ax1.get_xaxis().set_label_coords(0.5, offset[0])
        ax1.get_yaxis().set_label_coords(offset[1], 0.5)
    else:
        ax1.get_xaxis().set_label_coords(0.5, -0.08)
        ax1.get_yaxis().set_label_coords(-0.12, 0.5)

    if xlim:
        ax1.set_xlim(xlim[0], xlim[1])

    if ylim:
        ax1.set_ylim(ylim[0], ylim[1])

    if models is not None:
        count = 0

        model_dict = {}

        for j, item in enumerate(models):
            if item.library not in model_dict:
                model_dict[item.library] = [count, 0]
                count += 1

            else:
                model_dict[item.library] = [
                    model_dict[item.library][0],
                    model_dict[item.library][1] + 1
                ]

            model_count = model_dict[item.library]

            if model_count[1] == 0:
                label = plot_util.model_name(item.library)

                if item.library == 'zhu2015':
                    ax1.plot(item.color1,
                             item.color2,
                             marker='x',
                             ms=5,
                             linestyle=model_linestyle[model_count[1]],
                             linewidth=0.6,
                             color='gray',
                             label=label,
                             zorder=0)

                    xlim = ax1.get_xlim()
                    ylim = ax1.get_ylim()

                    for i, teff_item in enumerate(item.sptype):
                        teff_label = rf'{teff_item:.0e} $M_\mathregular{{Jup}}^{2}$ yr$^{{-1}}$'

                        if item.color2[i] < ylim[1]:
                            ax1.annotate(teff_label,
                                         (item.color1[i], item.color2[i]),
                                         color='gray',
                                         fontsize=8,
                                         ha='left',
                                         va='center',
                                         xytext=(item.color1[i] + 0.1,
                                                 item.color2[i] - 0.05),
                                         zorder=3)

                else:
                    ax1.plot(item.color1,
                             item.color2,
                             linestyle=model_linestyle[model_count[1]],
                             linewidth=1.,
                             color=model_color[model_count[0]],
                             label=label,
                             zorder=0)

                    if mass_labels is not None:
                        interp_color1 = interp1d(item.sptype, item.color1)
                        interp_color2 = interp1d(item.sptype, item.color2)

                        for i, mass_item in enumerate(mass_labels):
                            if isinstance(mass_item, tuple):
                                mass_val = mass_item[0]
                                mass_pos = mass_item[1]

                            else:
                                mass_val = mass_item
                                mass_pos = 'right'

                            # if j == 0 or (j > 0 and mass_val < 20.):
                            if j == 0:
                                pos_color1 = interp_color1(mass_val)
                                pos_color2 = interp_color2(mass_val)

                                if mass_pos == 'left':
                                    mass_ha = 'right'
                                    mass_xytext = (pos_color1 - 0.05,
                                                   pos_color2)

                                else:
                                    mass_ha = 'left'
                                    mass_xytext = (pos_color1 + 0.05,
                                                   pos_color2)

                                mass_label = str(
                                    int(mass_val)) + r' M$_\mathregular{J}$'

                                xlim = ax1.get_xlim()
                                ylim = ax1.get_ylim()

                                if xlim[0]+0.2 < pos_color1 < xlim[1]-0.2 and \
                                        ylim[0]+0.2 < pos_color2 < ylim[1]-0.2:

                                    ax1.scatter(pos_color1,
                                                pos_color2,
                                                c=model_color[model_count[0]],
                                                s=15,
                                                edgecolor='none',
                                                zorder=0)

                                    ax1.annotate(
                                        mass_label, (pos_color1, pos_color2),
                                        color=model_color[model_count[0]],
                                        fontsize=9,
                                        xytext=mass_xytext,
                                        ha=mass_ha,
                                        va='center',
                                        zorder=3)

            else:
                ax1.plot(item.color1,
                         item.color2,
                         linestyle=model_linestyle[model_count[1]],
                         linewidth=0.6,
                         color=model_color[model_count[0]],
                         label=label,
                         zorder=0)

    if planck is not None:
        planck_count = 0

        for j, item in enumerate(planck):

            if planck_count == 0:
                label = plot_util.model_name(item.library)

                ax1.plot(item.color1,
                         item.color2,
                         ls='--',
                         linewidth=0.8,
                         color='black',
                         label=label,
                         zorder=0)

                if teff_labels is not None:
                    interp_color1 = interp1d(item.sptype, item.color1)
                    interp_color2 = interp1d(item.sptype, item.color2)

                    for i, teff_item in enumerate(teff_labels):
                        if isinstance(teff_item, tuple):
                            teff_val = teff_item[0]
                            teff_pos = teff_item[1]

                        else:
                            teff_val = teff_item
                            teff_pos = 'right'

                        if j == 0 or (j > 0 and teff_val < 20.):
                            pos_color1 = interp_color1(teff_val)
                            pos_color2 = interp_color2(teff_val)

                            if teff_pos == 'left':
                                teff_ha = 'right'
                                teff_xytext = (pos_color1 - 0.05, pos_color2)

                            else:
                                teff_ha = 'left'
                                teff_xytext = (pos_color1 + 0.05, pos_color2)

                            teff_label = f'{int(teff_val)} K'

                            xlim = ax1.get_xlim()
                            ylim = ax1.get_ylim()

                            if xlim[0]+0.2 < pos_color1 < xlim[1]-0.2 and \
                                    ylim[0]+0.2 < pos_color2 < ylim[1]-0.2:

                                ax1.scatter(pos_color1,
                                            pos_color2,
                                            c='black',
                                            s=15,
                                            edgecolor='none',
                                            zorder=0)

                                ax1.annotate(teff_label,
                                             (pos_color1, pos_color2),
                                             color='black',
                                             fontsize=9,
                                             xytext=teff_xytext,
                                             zorder=3,
                                             ha=teff_ha,
                                             va='center')

            else:
                ax1.plot(item.color1,
                         item.color2,
                         ls='--',
                         lw=0.5,
                         color='black',
                         zorder=0)

            planck_count += 1

    if empirical:
        cmap = plt.cm.viridis

        bounds, ticks, ticklabels = plot_util.field_bounds_ticks(field_range)
        norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

        for item in empirical:
            sptype = item.sptype
            names = item.names
            color1 = item.color1
            color2 = item.color2

            if isinstance(sptype, list):
                sptype = np.array(sptype)

            if item.object_type in ['field', None]:
                indices = np.where(sptype != 'None')[0]

                sptype = sptype[indices]
                color1 = color1[indices]
                color2 = color2[indices]

                spt_disc = plot_util.sptype_substellar(sptype, color1.shape)
                _, unique = np.unique(color1, return_index=True)

                sptype = sptype[unique]
                color1 = color1[unique]
                color2 = color2[unique]
                spt_disc = spt_disc[unique]

                scat = ax1.scatter(color1,
                                   color2,
                                   c=spt_disc,
                                   cmap=cmap,
                                   norm=norm,
                                   s=50,
                                   alpha=0.7,
                                   edgecolor='none',
                                   zorder=2)

                cb = Colorbar(ax=ax2,
                              mappable=scat,
                              orientation='horizontal',
                              ticklocation='top',
                              format='%.2f')

                cb.ax.tick_params(width=1,
                                  length=5,
                                  labelsize=10,
                                  direction='in',
                                  color='black')

                cb.set_ticks(ticks)
                cb.set_ticklabels(ticklabels)

            elif item.object_type == 'young':
                if objects is not None:
                    object_names = []

                    for obj_item in objects:
                        object_names.append(obj_item[0])

                    indices = plot_util.remove_color_duplicates(
                        object_names, names)

                    color1 = color1[indices]
                    color2 = color2[indices]

                ax1.plot(color1,
                         color2,
                         marker='s',
                         ms=4,
                         linestyle='none',
                         alpha=0.7,
                         color='gray',
                         markeredgecolor='black',
                         label='Young/low-gravity',
                         zorder=2)

    if isochrones:
        for item in isochrones:
            ax1.plot(item.colors[0],
                     item.colors[1],
                     linestyle='-',
                     linewidth=1.,
                     color='black')

    if reddening is not None:
        for item in reddening:
            ext_1, ext_2 = dust_util.calc_reddening(item[0],
                                                    item[2],
                                                    composition=item[3],
                                                    structure='crystalline',
                                                    radius_g=item[4])

            ext_3, ext_4 = dust_util.calc_reddening(item[1],
                                                    item[2],
                                                    composition=item[3],
                                                    structure='crystalline',
                                                    radius_g=item[4])

            delta_x = ext_1 - ext_2
            delta_y = ext_3 - ext_4

            x_pos = item[5][0] + delta_x
            y_pos = item[5][1] + delta_y

            ax1.annotate('', (x_pos, y_pos),
                         xytext=(item[5][0], item[5][1]),
                         fontsize=8,
                         arrowprops={'arrowstyle': '->'},
                         color='black',
                         zorder=3.)

            x_pos_text = item[5][0] + delta_x / 2.
            y_pos_text = item[5][1] + delta_y / 2.

            vector_len = math.sqrt(delta_x**2 + delta_y**2)

            if item[3] == 'MgSiO3':
                dust_species = r'MgSiO$_{3}$'

            elif item[3] == 'Fe':
                dust_species = 'Fe'

            if item[4].is_integer():
                red_label = f'{dust_species} ({item[4]:.0f} µm)'

            else:
                red_label = f'{dust_species} ({item[4]:.1f} µm)'

            text = ax1.annotate(red_label, (x_pos_text, y_pos_text),
                                xytext=(-7. * delta_y / vector_len,
                                        7. * delta_x / vector_len),
                                textcoords='offset points',
                                fontsize=8.,
                                color='black',
                                ha='center',
                                va='center')

            ax1.plot([item[5][0], x_pos], [item[5][1], y_pos],
                     '-',
                     color='white')

            sp1 = ax1.transData.transform_point((item[5][0], item[5][1]))
            sp2 = ax1.transData.transform_point((x_pos, y_pos))

            angle = np.degrees(np.arctan2(sp2[1] - sp1[1], sp2[0] - sp1[0]))
            text.set_rotation(angle)

    if objects is not None:
        for i, item in enumerate(objects):
            objdata = read_object.ReadObject(item[0])

            objphot1 = objdata.get_photometry(item[1][0])
            objphot2 = objdata.get_photometry(item[1][1])
            objphot3 = objdata.get_photometry(item[2][0])
            objphot4 = objdata.get_photometry(item[2][1])

            if objphot1.ndim == 2:
                print(
                    f'Found {objphot1.shape[1]} values for filter {item[1][0]} of {item[0]}'
                )
                print(
                    f'so using the first value:  {objphot1[0, 0]} +/- {objphot1[1, 0]} mag'
                )
                objphot1 = objphot1[:, 0]

            if objphot2.ndim == 2:
                print(
                    f'Found {objphot2.shape[1]} values for filter {item[1][1]} of {item[0]}'
                )
                print(
                    f'so using the first value:  {objphot2[0, 0]} +/- {objphot2[1, 0]} mag'
                )
                objphot2 = objphot2[:, 0]

            if objphot3.ndim == 2:
                print(
                    f'Found {objphot3.shape[1]} values for filter {item[2][0]} of {item[0]}'
                )
                print(
                    f'so using the first value:  {objphot3[0, 0]} +/- {objphot3[1, 0]} mag'
                )
                objphot3 = objphot3[:, 0]

            if objphot4.ndim == 2:
                print(
                    f'Found {objphot4.shape[1]} values for filter {item[2][1]} of {item[0]}'
                )
                print(
                    f'so using the first value:  {objphot4[0, 0]} +/- {objphot4[1, 0]} mag'
                )
                objphot4 = objphot4[:, 0]

            color1 = objphot1[0] - objphot2[0]
            color2 = objphot3[0] - objphot4[0]

            error1 = math.sqrt(objphot1[1]**2 + objphot2[1]**2)
            error2 = math.sqrt(objphot3[1]**2 + objphot4[1]**2)

            if len(item) > 3 and item[3] is not None:
                kwargs = item[3]

            else:
                kwargs = {
                    'marker': '>',
                    'ms': 6.,
                    'color': 'black',
                    'mfc': 'white',
                    'mec': 'black',
                    'label': 'Direct imaging'
                }

            ax1.errorbar(color1,
                         color2,
                         xerr=error1,
                         yerr=error2,
                         zorder=3,
                         **kwargs)

            if companion_labels:
                if len(item) > 3:
                    kwargs = item[4]

                else:
                    kwargs = {
                        'ha': 'left',
                        'va': 'bottom',
                        'fontsize': 8.5,
                        'xytext': (5., 5.),
                        'color': 'black'
                    }

                ax1.annotate(objdata.object_name, (color1, color2),
                             zorder=3,
                             textcoords='offset points',
                             **kwargs)

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

    handles, labels = ax1.get_legend_handles_labels()

    if legend is not None:
        handles, labels = ax1.get_legend_handles_labels()

        # prevent duplicates
        by_label = dict(zip(labels, handles))

        if handles:
            ax1.legend(by_label.values(),
                       by_label.keys(),
                       loc=legend,
                       fontsize=8.5,
                       frameon=False,
                       numpoints=1)

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

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

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

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

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

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

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

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

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

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

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

            Calibration parameters:

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

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

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

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

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

            ISM extinction parameters:

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

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

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

                 - Only supported by ``run_multinest``.

            Log-normal size distribution:

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

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

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

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

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

                 - Only supported by ``run_multinest``.

            Power-law size distribution:

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

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

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

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

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

                 - Only supported by ``run_multinest``.

        inc_phot : bool, list(str)
            Include photometric data in the fit. If a boolean, either all (``True``) or none
            (``False``) of the data are selected. If a list, a subset of filter names (as stored in
            the database) can be provided.
        inc_spec : bool, list(str)
            Include spectroscopic data in the fit. If a boolean, either all (``True``) or none
            (``False``) of the data are selected. If a list, a subset of spectrum names (as stored
            in the database with :func:`~species.data.database.Database.add_object`) can be
            provided.
        fit_corr : list(str), None
            List with spectrum names for which the correlation length and fractional amplitude are
            fitted (see Wang et al. 2020).

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

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

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

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

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

        self.model = model
        self.bounds = bounds

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

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

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

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

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

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

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

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

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

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

            self.n_planck = 0

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

        # Select filters and spectra

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

            else:
                inc_phot = []

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

            else:
                inc_spec = []

        # Include photometric data

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

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

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

            print(' [DONE]')

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

        # Include spectroscopic data

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

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

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

            self.n_corr_par = 0

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

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

                    self.n_corr_par += 2

            self.modelspec = []

            if self.model != 'planck':

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

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

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

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

                    self.modelspec.append(readmodel)

                    print(' [DONE]')

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

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

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

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

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

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

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

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

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

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

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

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

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

        else:
            self.cross_sections = None

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

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

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

        print('Prior boundaries:')

        for key, value in self.bounds.items():
            print(f'   - {key} = {value}')
示例#7
0
def plot_grid_statistic(
    tag: str,
    upsample: bool = False,
    xlim: Optional[Tuple[float, float]] = None,
    ylim: Optional[Tuple[float, float]] = None,
    title: Optional[str] = None,
    offset: Optional[Tuple[float, float]] = None,
    figsize: Optional[Tuple[float, float]] = (4.0, 2.5),
    output: Optional[str] = "grid_statistic.pdf",
):
    """
    Function for plotting the results from the empirical spectrum comparison.

    Parameters
    ----------
    tag : str
        Database tag where the results from the empirical comparison with
        :class:`~species.analysis.empirical.CompareSpectra.spectral_type` are stored.
    upsample : bool
        Upsample the goodness-of-fit grid to a higher resolution for a smoother appearance.
    xlim : tuple(float, float)
        Limits of the spectral type axis.
    ylim : tuple(float, float)
        Limits of the goodness-of-fit axis.
    title : str
        Plot title.
    offset : tuple(float, float)
        Offset for the label of the x- and y-axis.
    figsize : tuple(float, float)
        Figure size.
    output : str
        Output filename for the plot. The plot is shown in an
        interface window if the argument is set to ``None``.

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

    if output is None:
        print("Plotting goodness-of-fit of model grid...", end="")
    else:
        print(f"Plotting goodness-of-fit of model grid: {output}...", end="")

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

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

    db_path = config["species"]["database"]

    h5_file = h5py.File(db_path, "r")

    dset = h5_file[f"results/comparison/{tag}/goodness_of_fit"]

    n_param = dset.attrs["n_param"]

    # flux_scaling = np.array(h5_file[f'results/comparison/{tag}/flux_scaling'])

    # if 'extra_scaling' in h5_file[f'results/comparison/{tag}']:
    #     extra_scaling = np.array(h5_file[f'results/comparison/{tag}/extra_scaling'])
    # else:
    #     extra_scaling = None

    read_obj = read_object.ReadObject(dset.attrs["object_name"])

    n_wavel = 0
    for item in read_obj.get_spectrum().values():
        n_wavel += item[0].shape[0]

    goodness_fit = np.array(dset)

    model_param = []
    coord_points = []

    for i in range(n_param):
        model_param.append(dset.attrs[f"parameter{i}"])
        coord_points.append(
            np.array(h5_file[f"results/comparison/{tag}/coord_points{i}"])
        )

    coord_x = coord_points[0]

    if len(coord_points[1]) > 1:
        coord_y = coord_points[1]
    elif len(coord_points[2]) > 1:
        coord_y = coord_points[2]
    else:
        coord_y = None

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

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

    plt.figure(1, figsize=figsize)

    if coord_y is None:
        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])

    else:
        gridsp = mpl.gridspec.GridSpec(1, 2, width_ratios=[4.0, 0.25])
        gridsp.update(wspace=0.07, hspace=0, left=0, right=1, bottom=0, top=1)

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

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

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

    ax.xaxis.set_minor_locator(AutoMinorLocator(5))
    ax.yaxis.set_minor_locator(AutoMinorLocator(5))

    ax.set_xlabel(r"T$_\mathregular{eff}$ (K)", fontsize=13.0)

    if coord_y is None:
        ax.set_ylabel(r"$\Delta\mathregular{log}\,\mathregular{G}$", fontsize=13.0)

    elif len(coord_points[1]) > 1:
        ax.set_ylabel(r"$\mathregular{log}\,\mathregular{g}$", fontsize=13.0)

    elif len(coord_points[2]) > 1:
        ax.set_ylabel(r"$\mathregular{A}_\mathregular{V}$", fontsize=13.0)

    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.11)
        ax.get_yaxis().set_label_coords(-0.1, 0.5)

    if title is not None:
        ax.set_title(title, y=1.02, fontsize=14.0)

    # Sum/collapse over log(g) if it contains a single value
    if len(coord_points[1]) == 1:
        goodness_fit = np.sum(goodness_fit, axis=1)

    # Indices of the best-fit model
    best_index = np.unravel_index(goodness_fit.argmin(), goodness_fit.shape)

    # Make Teff the x axis and log(g) the y axis
    goodness_fit = np.transpose(goodness_fit)

    if len(coord_points[1]) > 1 and len(coord_points[2]) > 1:
        # Indices with the minimum G_k for the tested A_V values
        indices = np.argmin(goodness_fit, axis=0)

        # Select minimum G_k for tested A_V values
        goodness_fit = np.amin(goodness_fit, axis=0)

        extra_map = np.zeros(goodness_fit.shape)

        for i in range(extra_map.shape[0]):
            for j in range(extra_map.shape[1]):
                extra_map[i, j] = coord_points[2][indices[i, j]]

    if coord_y is not None:
        if upsample:
            fit_interp = RegularGridInterpolator((coord_y, coord_x), goodness_fit)

            x_new = np.linspace(coord_x[0], coord_x[-1], 50)
            y_new = np.linspace(coord_y[0], coord_y[-1], 50)

            x_grid, y_grid = np.meshgrid(x_new, y_new)

            goodness_fit = fit_interp((y_grid, x_grid))

        else:
            x_grid, y_grid = np.meshgrid(coord_x, coord_y)

    goodness_fit = np.log10(goodness_fit)
    goodness_fit -= np.amin(goodness_fit)

    if coord_y is None:
        ax.plot(
            coord_x,
            goodness_fit[
                0,
            ],
        )

    else:
        c = ax.contourf(x_grid, y_grid, goodness_fit, levels=20)

        cb = mpl.colorbar.Colorbar(
            ax=ax_cb,
            mappable=c,
            orientation="vertical",
            ticklocation="right",
            format="%.1f",
        )

        cb.ax.minorticks_on()

        cb.ax.tick_params(
            which="major",
            width=0.8,
            length=5,
            labelsize=12,
            direction="in",
            color="black",
        )

        cb.ax.tick_params(
            which="minor",
            width=0.8,
            length=3,
            labelsize=12,
            direction="in",
            color="black",
        )

        cb.ax.set_ylabel(
            r"$\Delta\mathregular{log}\,\mathregular{G}$",
            rotation=270,
            labelpad=22,
            fontsize=13.0,
        )

        if len(coord_points[1]) > 1 and len(coord_points[2]) > 1:
            if upsample:
                extra_interp = RegularGridInterpolator((coord_y, coord_x), extra_map)
                extra_map = extra_interp((y_grid, x_grid))

                cs = ax.contour(
                    x_grid, y_grid, extra_map, levels=10, colors="white", linewidths=0.7
                )

            else:
                cs = ax.contour(
                    coord_x,
                    coord_y,
                    extra_map,
                    levels=10,
                    colors="white",
                    linewidths=0.7,
                )

            # manual = [(2350, 0.8), (2500, 0.8), (2600, 0.8), (2700, 0.8),
            #           (2800, 0.8), (2950, 0.8), (3100, 0.8), (3300, 0.8)]

            ax.clabel(cs, cs.levels, inline=True, fontsize=8, fmt="%1.1f")

        # if extra_scaling is not None and len(coord_points[2]) > 1:
        #     ratio = np.transpose(flux_scaling[:, 0, :])/np.transpose(extra_scaling[:, 0, :, 0])
        #
        #     cs = ax.contour(coord_x, coord_y, ratio, levels=10, colors='white',
        #                     linestyles='-', linewidths=0.7)
        #
        #     ax.clabel(cs, cs.levels, inline=True, fontsize=8, fmt='%1.1f')

        ax.plot(
            coord_x[best_index[0]],
            coord_y[best_index[1]],
            marker="X",
            ms=10.0,
            color="#eb4242",
            mfc="#eb4242",
            mec="black",
        )

        # best_param = (coord_x[best_index[0]], coord_y[best_index[1]])
        #
        # par_key, par_unit, par_label = plot_util.quantity_unit(model_param, object_type='planet')
        #
        # par_text = f'{par_label[0]} = {best_param[0]:.0f} {par_unit[0]}\n' \
        #            f'{par_label[1]} = {best_param[1]:.1f}'
        #
        # ax.annotate(par_text, (best_param[0]+50., best_param[1]), ha='left', va='center',
        #             color='white', fontsize=12.)

    print(" [DONE]")

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

    plt.clf()
    plt.close()

    h5_file.close()
示例#8
0
def plot_empirical_spectra(
    tag: str,
    n_spectra: int,
    flux_offset: Optional[float] = None,
    label_pos: Optional[Tuple[float, float]] = None,
    xlim: Optional[Tuple[float, float]] = None,
    ylim: Optional[Tuple[float, float]] = None,
    title: Optional[str] = None,
    offset: Optional[Tuple[float, float]] = None,
    figsize: Optional[Tuple[float, float]] = (4.0, 2.5),
    output: Optional[str] = "empirical.pdf",
):
    """
    Function for plotting the results from the empirical spectrum comparison.

    Parameters
    ----------
    tag : str
        Database tag where the results from the empirical comparison with
        :class:`~species.analysis.empirical.CompareSpectra.spectral_type` are stored.
    n_spectra : int
        The number of spectra with the lowest goodness-of-fit statistic that will be plotted in
        comparison with the data.
    label_pos : tuple(float, float), None
        Position for the name labels. Should be provided as (x, y) for the lowest spectrum. The
        ``flux_offset`` will be applied to the remaining spectra. The labels are only
        plotted if the argument of both ``label_pos`` and ``flux_offset`` are not ``None``.
    flux_offset : float, None
        Offset to be applied such that the spectra do not overlap. No offset is applied if the
        argument is set to ``None``.
    xlim : tuple(float, float)
        Limits of the spectral type axis.
    ylim : tuple(float, float)
        Limits of the goodness-of-fit axis.
    title : str
        Plot title.
    offset : tuple(float, float)
        Offset for the label of the x- and y-axis.
    figsize : tuple(float, float)
        Figure size.
    output : str
        Output filename for the plot. The plot is shown in an
        interface window if the argument is set to ``None``.

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

    if output is None:
        print("Plotting empirical spectra comparison...", end="")
    else:
        print(f"Plotting empirical spectra comparison: {output}...", end="")

    if flux_offset is None:
        flux_offset = 0.0

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

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

    db_path = config["species"]["database"]

    h5_file = h5py.File(db_path, "r")

    dset = h5_file[f"results/empirical/{tag}/names"]

    object_name = dset.attrs["object_name"]
    spec_library = dset.attrs["spec_library"]
    n_spec_name = dset.attrs["n_spec_name"]

    spec_name = []
    for i in range(n_spec_name):
        spec_name.append(dset.attrs[f"spec_name{i}"])

    names = np.array(dset)
    flux_scaling = np.array(h5_file[f"results/empirical/{tag}/flux_scaling"])
    av_ext = np.array(h5_file[f"results/empirical/{tag}/av_ext"])

    rad_vel = np.array(h5_file[f"results/empirical/{tag}/rad_vel"])
    rad_vel *= 1e3  # (m s-1)

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

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

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

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

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

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

    ax.xaxis.set_minor_locator(AutoMinorLocator(5))
    ax.yaxis.set_minor_locator(AutoMinorLocator(5))

    ax.set_xlabel("Wavelength (µm)", fontsize=13)

    if flux_offset == 0.0:
        ax.set_ylabel(r"$\mathregular{F}_\lambda$ (W m$^{-2}$ µm$^{-1}$)", fontsize=11)
    else:
        ax.set_ylabel(
            r"$\mathregular{F}_\lambda$ (W m$^{-2}$ µm$^{-1}$) + offset", fontsize=11
        )

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

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

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

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

    if title is not None:
        ax.set_title(title, y=1.02, fontsize=13)

    read_obj = read_object.ReadObject(object_name)

    obj_spec = []
    obj_res = []

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

    if flux_offset == 0.0:
        for spec_item in obj_spec:
            ax.plot(spec_item[:, 0], spec_item[:, 1], "-", lw=0.5, color="black")

    for i in range(n_spectra):
        if isinstance(names[i], str):
            name_item = names[i]
        else:
            name_item = names[i].decode("utf-8")

        dset = h5_file[f"spectra/{spec_library}/{name_item}"]
        sptype = dset.attrs["sptype"]
        spectrum = np.asarray(dset)

        if flux_offset != 0.0:
            for spec_item in obj_spec:
                ax.plot(
                    spec_item[:, 0],
                    (n_spectra - i - 1) * flux_offset + spec_item[:, 1],
                    "-",
                    lw=0.5,
                    color="black",
                )

        for j, spec_item in enumerate(obj_spec):
            ism_ext = dust_util.ism_extinction(av_ext[i], 3.1, spectrum[:, 0])
            ext_scaling = 10.0 ** (-0.4 * ism_ext)

            wavel_shifted = (
                spectrum[:, 0] + spectrum[:, 0] * rad_vel[i] / constants.LIGHT
            )

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

            interp_spec = interp1d(
                spectrum[:, 0], flux_smooth, fill_value="extrapolate"
            )

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

            flux_resample = interp_spec(obj_spec[j][indices, 0])

            ax.plot(
                obj_spec[j][indices, 0],
                (n_spectra - i - 1) * flux_offset + flux_scaling[i][j] * flux_resample,
                color="tomato",
                lw=0.5,
            )

        if label_pos is not None and flux_offset != 0.0:
            label_text = name_item + ", " + sptype

            if av_ext[i] != 0.0:
                label_text += r", A$_\mathregular{V}$ = " + f"{av_ext[i]:.1f}"

            ax.text(
                label_pos[0],
                label_pos[1] + (n_spectra - i - 1) * flux_offset,
                label_text,
                fontsize=8.0,
                ha="left",
            )

    print(" [DONE]")

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

    plt.clf()
    plt.close()

    h5_file.close()
示例#9
0
def plot_color_color(colorbox,
                     objects,
                     label_x,
                     label_y,
                     output,
                     xlim=None,
                     ylim=None,
                     offset=None,
                     legend='upper left'):
    """
    Parameters
    ----------
    colorbox : species.core.box.ColorMagBox
        Box with the colors and magnitudes.
    objects : tuple(tuple(str, str, str, str), )
        Tuple with individual objects. The objects require a tuple with their database tag, the
        two filter IDs for the color, and the filter ID for the absolute magnitude.
    label_x : str
        Label for the x-axis.
    label_y : str
        Label for the y-axis.
    output : str
        Output filename.
    xlim : tuple(float, float)
        Limits for the x-axis.
    ylim : tuple(float, float)
        Limits for the y-axis.
    offset : tuple(float, float)
        Offset of the x- and y-axis label.
    legend : str
        Legend position.

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

    marker = itertools.cycle(('o', 's', '<', '>', 'p', 'v', '^', '*',
                              'd', 'x', '+', '1', '2', '3', '4'))

    sys.stdout.write('Plotting color-color diagram: '+output+'... ')
    sys.stdout.flush()

    plt.figure(1, figsize=(4, 4.3))
    gridsp = mpl.gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.1, 4.])
    gridsp.update(wspace=0., hspace=0., left=0, right=1, bottom=0, top=1)

    ax1 = plt.subplot(gridsp[2, 0])
    ax2 = plt.subplot(gridsp[0, 0])

    ax1.grid(True, linestyle=':', linewidth=0.7, color='silver', dashes=(1, 4), zorder=0)

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

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

    ax1.set_xlabel(label_x, fontsize=14)
    ax1.set_ylabel(label_y, fontsize=14)

    ax1.invert_yaxis()

    if offset:
        ax1.get_xaxis().set_label_coords(0.5, offset[0])
        ax1.get_yaxis().set_label_coords(offset[1], 0.5)
    else:
        ax1.get_xaxis().set_label_coords(0.5, -0.08)
        ax1.get_yaxis().set_label_coords(-0.12, 0.5)

    if xlim:
        ax1.set_xlim(xlim[0], xlim[1])

    if ylim:
        ax1.set_ylim(ylim[0], ylim[1])

    cmap = plt.cm.viridis
    bounds = np.arange(0, 8, 1)
    norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

    sptype = colorbox.sptype
    color1 = colorbox.color1
    color2 = colorbox.color2

    indices = np.where(sptype != 'None')[0]

    sptype = sptype[indices]
    color1 = color1[indices]
    color2 = color2[indices]

    spt_disc = plot_util.sptype_discrete(sptype, color1.shape)

    _, unique = np.unique(color1, return_index=True)

    sptype = sptype[unique]
    color1 = color1[unique]
    color2 = color2[unique]
    spt_disc = spt_disc[unique]

    scat = ax1.scatter(color1, color2, c=spt_disc, cmap=cmap, norm=norm,
                       zorder=5, s=40, alpha=0.6, edgecolor='none')

    cb = Colorbar(ax=ax2, mappable=scat, orientation='horizontal',
                  ticklocation='top', format='%.2f')

    cb.ax.tick_params(width=0.8, length=5, labelsize=10, direction='in', color='black')
    cb.set_ticks(np.arange(0.5, 7., 1.))
    cb.set_ticklabels(['M0-M4', 'M5-M9', 'L0-L4', 'L5-L9', 'T0-T4', 'T6-T8', 'Y1-Y2'])

    if objects is not None:
        for item in objects:
            objdata = read_object.ReadObject(item[0])

            mag1 = objdata.get_photometry(item[1][0])[0]
            mag2 = objdata.get_photometry(item[1][1])[0]
            mag3 = objdata.get_photometry(item[2][0])[0]
            mag4 = objdata.get_photometry(item[2][1])[0]

            err1 = objdata.get_photometry(item[1][0])[1]
            err2 = objdata.get_photometry(item[1][1])[1]
            err3 = objdata.get_photometry(item[2][0])[1]
            err4 = objdata.get_photometry(item[2][1])[1]

            color1 = mag1 - mag2
            color2 = mag3 - mag4

            error1 = math.sqrt(err1**2+err2**2)
            error2 = math.sqrt(err3**2+err4**2)

            ax1.errorbar(color1, color2, xerr=error1, yerr=error2,
                         marker=next(marker), ms=6, color='black', label=objdata.object_name,
                         markerfacecolor='white', markeredgecolor='black', zorder=10)

    handles, labels = ax1.get_legend_handles_labels()

    if handles:
        handles = [h[0] for h in handles]
        ax1.legend(handles, labels, loc=legend, prop={'size': 9}, frameon=False, numpoints=1)

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

    sys.stdout.write('[DONE]\n')
    sys.stdout.flush()
示例#10
0
def plot_color_magnitude(colorbox=None,
                         objects=None,
                         isochrones=None,
                         models=None,
                         label_x='color [mag]',
                         label_y='M [mag]',
                         xlim=None,
                         ylim=None,
                         offset=None,
                         legend='upper left',
                         output='color-magnitude.pdf'):
    """
    Parameters
    ----------
    colorbox : species.core.box.ColorMagBox, None
        Box with the colors and magnitudes.
    objects : tuple(tuple(str, str, str, str), ), None
        Tuple with individual objects. The objects require a tuple with their database tag, the two
        filter IDs for the color, and the filter ID for the absolute magnitude.
    isochrones : tuple(species.core.box.IsochroneBox, ), None
        Tuple with boxes of isochrone data. Not used if set to None.
    models : tuple(species.core.box.ColorMagBox, ), None

    label_x : str
        Label for the x-axis.
    label_y : str
        Label for the y-axis.
    xlim : tuple(float, float)
        Limits for the x-axis.
    ylim : tuple(float, float)
        Limits for the y-axis.
    legend : str
        Legend position.
    output : str
        Output filename.

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

    marker = itertools.cycle(('o', 's', '<', '>', 'p', 'v', '^', '*',
                              'd', 'x', '+', '1', '2', '3', '4'))

    model_color = ('tomato', 'teal', 'dodgerblue')
    model_linestyle = ('-', '--', ':', '-.')

    sys.stdout.write('Plotting color-magnitude diagram: '+output+'... ')
    sys.stdout.flush()

    if (models is not None and colorbox is None) or \
            (models is not None and colorbox.object_type == 'temperature'):
        plt.figure(1, figsize=(4.4, 4.5))
        gridsp = mpl.gridspec.GridSpec(1, 3, width_ratios=[4, 0.15, 0.25])
        gridsp.update(wspace=0., hspace=0., left=0, right=1, bottom=0, top=1)

        ax1 = plt.subplot(gridsp[0, 0])
        ax2 = plt.subplot(gridsp[0, 2])

    elif colorbox.object_type != 'temperature':
        plt.figure(1, figsize=(4., 4.8))
        gridsp = mpl.gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.1, 4.5])
        gridsp.update(wspace=0., hspace=0., left=0, right=1, bottom=0, top=1)

        ax1 = plt.subplot(gridsp[2, 0])
        ax2 = plt.subplot(gridsp[0, 0])

    # elif models is not None and colorbox.object_type != 'temperature':
    #     plt.figure(1, figsize=(4.2, 4.8))
    #     gridsp = mpl.gridspec.GridSpec(3, 3, width_ratios=[3.7, 0.15, 0.25], height_ratios=[0.25, 0.15, 4.4])
    #     gridsp.update(wspace=0., hspace=0., left=0, right=1, bottom=0, top=1)
    #
    #     ax1 = plt.subplot(gridsp[2, 0])
    #     ax2 = plt.subplot(gridsp[0, 0])
    #     ax3 = plt.subplot(gridsp[2, 2])

    if colorbox is not None:
        sptype = colorbox.sptype
        color = colorbox.color
        magnitude = colorbox.magnitude

    ax1.grid(True, linestyle=':', linewidth=0.7, color='silver', dashes=(1, 4), zorder=0)

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

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

    ax1.set_xlabel(label_x, fontsize=14)
    ax1.set_ylabel(label_y, fontsize=14)

    ax1.invert_yaxis()

    if offset:
        ax1.get_xaxis().set_label_coords(0.5, offset[0])
        ax1.get_yaxis().set_label_coords(offset[1], 0.5)
    else:
        ax1.get_xaxis().set_label_coords(0.5, -0.08)
        ax1.get_yaxis().set_label_coords(-0.12, 0.5)

    if xlim:
        ax1.set_xlim(xlim[0], xlim[1])

    if ylim:
        ax1.set_ylim(ylim[0], ylim[1])

    if colorbox is not None:
        cmap_sptype = plt.cm.viridis

        if colorbox.object_type == 'star':
            bounds_sptype = np.arange(0, 11, 1)
        else:
            bounds_sptype = np.arange(0, 8, 1)

    if colorbox.object_type != 'temperature':
        norm_sptype = mpl.colors.BoundaryNorm(bounds_sptype, cmap_sptype.N)

        indices = np.where(sptype != b'None')[0]

        sptype = sptype[indices]
        color = color[indices]
        magnitude = magnitude[indices]

        if colorbox.object_type == 'star':
            spt_disc = plot_util.sptype_stellar(sptype, color.shape)
            unique = np.arange(0, color.size, 1)

        elif colorbox.object_type != 'temperature':
            spt_disc = plot_util.sptype_substellar(sptype, color.shape)
            _, unique = np.unique(color, return_index=True)

        if colorbox.object_type == 'temperature':
            scat_sptype = ax1.scatter(color, magnitude, c=sptype, cmap=cmap_sptype,
                                      zorder=6, s=40, alpha=0.6, edgecolor='none')

        else:
            sptype = sptype[unique]
            color = color[unique]
            magnitude = magnitude[unique]
            spt_disc = spt_disc[unique]

            scat_sptype = ax1.scatter(color, magnitude, c=spt_disc, cmap=cmap_sptype,
                                      norm=norm_sptype, zorder=6, s=40, alpha=0.6,
                                      edgecolor='none')

    if colorbox is not None:
        if colorbox.object_type == 'temperature':
            cb1 = Colorbar(ax=ax2, mappable=scat_sptype, orientation='vertical',
                           ticklocation='right', format='%i')

            cb1.ax.tick_params(width=0.8, length=5, labelsize=10, direction='in', color='black')
            cb1.ax.set_ylabel('Temperature [K]', rotation=270, fontsize=12, labelpad=22)
            cb1.solids.set_edgecolor("face")

        else:
            cb1 = Colorbar(ax=ax2, mappable=scat_sptype, orientation='horizontal',
                           ticklocation='top', format='%.2f')

            cb1.ax.tick_params(width=0.8, length=5, labelsize=10, direction='in', color='black')

            if colorbox.object_type == 'star':
                cb1.set_ticks(np.arange(0.5, 10., 1.))
                cb1.set_ticklabels(['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L', 'T', 'Y'])

            else:
                cb1.set_ticks(np.arange(0.5, 7., 1.))
                cb1.set_ticklabels(['M0-M4', 'M5-M9', 'L0-L4', 'L5-L9', 'T0-T4', 'T6-T8', 'Y1-Y2'])

    if models is not None:
        cmap_teff = plt.cm.afmhot

        teff_min = np.inf
        teff_max = -np.inf

        for item in models:

            if np.amin(item.sptype) < teff_min:
                teff_min = np.amin(item.sptype)

            if np.amax(item.sptype) > teff_max:
                teff_max = np.amax(item.sptype)

        norm_teff = mpl.colors.Normalize(vmin=teff_min, vmax=teff_max)

        count = 0

        model_dict = {}

        for item in models:
            if item.library not in model_dict:
                model_dict[item.library] = [count, 0]
                count += 1

            else:
                model_dict[item.library] = [model_dict[item.library][0], model_dict[item.library][1]+1]

            model_count = model_dict[item.library]

            if model_count[1] == 0:
                label = plot_util.model_name(item.library)

                ax1.plot(item.color, item.magnitude, linestyle=model_linestyle[model_count[1]],
                         linewidth=0.6, zorder=3, color=model_color[model_count[0]], label=label)

            else:
                ax1.plot(item.color, item.magnitude, linestyle=model_linestyle[model_count[1]],
                         linewidth=0.6, zorder=3, color=model_color[model_count[0]])

            # scat_teff = ax1.scatter(item.color, item.magnitude, c=item.sptype, cmap=cmap_teff,
            #                         norm=norm_teff, zorder=4, s=15, alpha=1.0, edgecolor='none')

        # cb2 = ColorbarBase(ax=ax3, cmap=cmap_teff, norm=norm_teff, orientation='vertical', ticklocation='right')
        # cb2.ax.tick_params(width=0.8, length=5, labelsize=10, direction='in', color='black')
        # cb2.ax.set_ylabel('Temperature [K]', rotation=270, fontsize=12, labelpad=22)

    if isochrones is not None:
        for item in isochrones:
            ax1.plot(item.color, item.magnitude, linestyle='-', linewidth=1., color='black')

    if objects is not None:
        for item in objects:
            objdata = read_object.ReadObject(item[0])

            objcolor1 = objdata.get_photometry(item[1])
            objcolor2 = objdata.get_photometry(item[2])
            abs_mag = objdata.get_absmag(item[3])

            colorerr = math.sqrt(objcolor1[1]**2+objcolor2[1]**2)

            ax1.errorbar(objcolor1[0]-objcolor2[0], abs_mag[0], yerr=abs_mag[1], xerr=colorerr,
                         marker=next(marker), ms=6, color='black', label=objdata.object_name,
                         markerfacecolor='white', markeredgecolor='black', zorder=10)

    handles, labels = ax1.get_legend_handles_labels()

    if handles:
        # handles = [h[0] for h in handles]
        # ax1.legend(handles, labels, loc=legend, prop={'size': 9}, frameon=False, numpoints=1)

        ax1.legend(loc=legend, prop={'size': 9}, frameon=False, numpoints=1)

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

    sys.stdout.write('[DONE]\n')
    sys.stdout.flush()
示例#11
0
    def __init__(
        self,
        object_name: str,
        spec_name: str,
        lambda_rest: float,
        wavel_range: Optional[Tuple[float, float]] = None,
    ) -> None:
        """
        Parameters
        ----------
        object_name : str
            Object name as stored in the database with
            :func:`~species.data.database.Database.add_object` or
            :func:`~species.data.database.Database.add_companion`.
        spec_name : str
            Name of the spectrum that is stored at the object data
            of ``object_name``.
        lambda_rest : float, None
            Rest wavelength (um) of the emission line. The parameter
            if used for calculating the radial velocity and its
            uncertainty.
        wavel_range : tuple(float, float), None
            Wavelength range (um) that is cropped from the
            spectrum. The full spectrum is used if the argument
            is set to ``None``.

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

        self.object_name = object_name
        self.spec_name = spec_name
        self.lambda_rest = lambda_rest

        self.object = read_object.ReadObject(object_name)
        self.parallax = self.object.get_parallax()[0]
        self.spectrum = self.object.get_spectrum()[spec_name][0]

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

        else:
            self.wavel_range = wavel_range

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

            self.spectrum = self.spectrum[
                indices,
            ]

        self.spec_vrad = (
            1e-3
            * constants.LIGHT
            * (self.spectrum[:, 0] - self.lambda_rest)
            / self.lambda_rest
        )

        self.continuum_flux = np.full(self.spectrum.shape[0], 0.0)
        self.continuum_check = False

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

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

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