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"]
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"]
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']
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]')
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')
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}')
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()
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()
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()
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()
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"]