def apply_ism_ext(wavelengths: np.ndarray, flux: np.ndarray, v_band_ext: float, v_band_red: float) -> np.ndarray: """ Internal function for applying ISM extinction to a spectrum. wavelengths : np.ndarray Wavelengths (um) of the spectrum. flux : np.ndarray Fluxes (W m-2 um-1) of the spectrum. v_band_ext : float Extinction (mag) in the V band. v_band_red : float Reddening in the V band. Returns ------- np.ndarray Fluxes (W m-2 um-1) with the extinction applied. """ ext_mag = dust_util.ism_extinction(v_band_ext, v_band_red, wavelengths) return flux * 10.**(-0.4*ext_mag)
def plot_extinction(tag: str, burnin: Optional[int] = None, random: Optional[int] = None, wavel_range: Optional[Tuple[float, float]] = None, xlim: Optional[Tuple[float, float]] = None, ylim: Optional[Tuple[float, float]] = None, offset: Optional[Tuple[float, float]] = None, output: str = 'extinction.pdf') -> None: """ Function to plot random samples of the extinction, either from fitting a size distribution of enstatite grains (``dust_radius``, ``dust_sigma``, and ``dust_ext``), or from fitting ISM extinction (``ism_ext`` and optionally ``ism_red``). Parameters ---------- tag : str Database tag with the samples. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. Only required after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`. random : int, None Number of randomly selected samples. All samples are used if set to ``None``. wavel_range : tuple(float, float), None Wavelength range (um) for the extinction. The default wavelength range (0.4, 10.) is used if set to ``None``. xlim : tuple(float, float), None Limits of the wavelength axis. The range is set automatically if set to ``None``. ylim : tuple(float, float) Limits of the extinction axis. The range is set automatically if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. output : str Output filename. Returns ------- NoneType None """ if burnin is None: burnin = 0 if wavel_range is None: wavel_range = (0.4, 10.) mpl.rcParams['font.serif'] = ['Bitstream Vera Serif'] mpl.rcParams['font.family'] = 'serif' plt.rc('axes', edgecolor='black', linewidth=2.2) species_db = database.Database() box = species_db.get_samples(tag) samples = box.samples if samples.ndim == 2 and random is not None: ran_index = np.random.randint(samples.shape[0], size=random) samples = samples[ran_index, ] elif samples.ndim == 3: if burnin > samples.shape[1]: raise ValueError( f'The \'burnin\' value is larger than the number of steps ' f'({samples.shape[1]}) that are made by the walkers.') samples = samples[:, burnin:, :] ran_walker = np.random.randint(samples.shape[0], size=random) ran_step = np.random.randint(samples.shape[1], size=random) samples = samples[ran_walker, ran_step, :] plt.figure(1, figsize=(6, 3)) gridsp = mpl.gridspec.GridSpec(1, 1) gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1) ax = plt.subplot(gridsp[0, 0]) ax.tick_params(axis='both', which='major', colors='black', labelcolor='black', direction='in', width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True) ax.tick_params(axis='both', which='minor', colors='black', labelcolor='black', direction='in', width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True) ax.set_xlabel('Wavelength (µm)', fontsize=12) ax.set_ylabel('Extinction (mag)', fontsize=12) if xlim is not None: ax.set_xlim(xlim[0], xlim[1]) if ylim is not None: ax.set_ylim(ylim[0], ylim[1]) if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.22) ax.get_yaxis().set_label_coords(-0.09, 0.5) sample_wavel = np.linspace(wavel_range[0], wavel_range[1], 100) if 'lognorm_radius' in box.parameters and 'lognorm_sigma' in box.parameters and \ 'lognorm_ext' in box.parameters: cross_optical, dust_radius, dust_sigma = dust_util.interp_lognorm([], [], None) log_r_index = box.parameters.index('lognorm_radius') sigma_index = box.parameters.index('lognorm_sigma') ext_index = box.parameters.index('lognorm_ext') log_r_g = samples[:, log_r_index] sigma_g = samples[:, sigma_index] dust_ext = samples[:, ext_index] database_path = dust_util.check_dust_database() with h5py.File(database_path, 'r') as h5_file: cross_section = np.asarray( h5_file['dust/lognorm/mgsio3/crystalline/cross_section']) wavelength = np.asarray( h5_file['dust/lognorm/mgsio3/crystalline/wavelength']) cross_interp = RegularGridInterpolator( (wavelength, dust_radius, dust_sigma), cross_section) for i in range(samples.shape[0]): cross_tmp = cross_optical['Generic/Bessell.V'](sigma_g[i], 10.**log_r_g[i]) n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.)) sample_cross = np.zeros(sample_wavel.shape) for j, item in enumerate(sample_wavel): sample_cross[j] = cross_interp( (item, 10.**log_r_g[i], sigma_g[i])) sample_ext = 2.5 * np.log10(np.exp(1.)) * sample_cross * n_grains ax.plot(sample_wavel, sample_ext, ls='-', lw=0.5, color='black', alpha=0.5) elif 'powerlaw_max' in box.parameters and 'powerlaw_exp' in box.parameters and \ 'powerlaw_ext' in box.parameters: cross_optical, dust_max, dust_exp = dust_util.interp_powerlaw([], [], None) r_max_index = box.parameters.index('powerlaw_max') exp_index = box.parameters.index('powerlaw_exp') ext_index = box.parameters.index('powerlaw_ext') r_max = samples[:, r_max_index] exponent = samples[:, exp_index] dust_ext = samples[:, ext_index] database_path = dust_util.check_dust_database() with h5py.File(database_path, 'r') as h5_file: cross_section = np.asarray( h5_file['dust/powerlaw/mgsio3/crystalline/cross_section']) wavelength = np.asarray( h5_file['dust/powerlaw/mgsio3/crystalline/wavelength']) cross_interp = RegularGridInterpolator( (wavelength, dust_max, dust_exp), cross_section) for i in range(samples.shape[0]): cross_tmp = cross_optical['Generic/Bessell.V'](exponent[i], 10.**r_max[i]) n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.)) sample_cross = np.zeros(sample_wavel.shape) for j, item in enumerate(sample_wavel): sample_cross[j] = cross_interp( (item, 10.**r_max[i], exponent[i])) sample_ext = 2.5 * np.log10(np.exp(1.)) * sample_cross * n_grains ax.plot(sample_wavel, sample_ext, ls='-', lw=0.5, color='black', alpha=0.5) elif 'ism_ext' in box.parameters: ext_index = box.parameters.index('ism_ext') ism_ext = samples[:, ext_index] if 'ism_red' in box.parameters: red_index = box.parameters.index('ism_red') ism_red = samples[:, red_index] else: ism_red = np.full(samples.shape[0], 3.1) for i in range(samples.shape[0]): sample_ext = dust_util.ism_extinction(ism_ext[i], ism_red[i], sample_wavel) ax.plot(sample_wavel, sample_ext, ls='-', lw=0.5, color='black', alpha=0.5) else: raise ValueError( 'The SamplesBox does not contain extinction parameters.') print(f'Plotting extinction: {output}...', end='', flush=True) plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight') plt.clf() plt.close() print(' [DONE]')
def plot_extinction( tag: str, burnin: Optional[int] = None, random: Optional[int] = None, wavel_range: Optional[Tuple[float, float]] = None, xlim: Optional[Tuple[float, float]] = None, ylim: Optional[Tuple[float, float]] = None, offset: Optional[Tuple[float, float]] = None, output: Optional[str] = "extinction.pdf", ) -> None: """ Function to plot random samples of the extinction, either from fitting a size distribution of enstatite grains (``dust_radius``, ``dust_sigma``, and ``dust_ext``), or from fitting ISM extinction (``ism_ext`` and optionally ``ism_red``). Parameters ---------- tag : str Database tag with the samples. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. Only required after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`. random : int, None Number of randomly selected samples. All samples are used if set to ``None``. wavel_range : tuple(float, float), None Wavelength range (um) for the extinction. The default wavelength range (0.4, 10.) is used if set to ``None``. xlim : tuple(float, float), None Limits of the wavelength axis. The range is set automatically if set to ``None``. ylim : tuple(float, float) Limits of the extinction axis. The range is set automatically if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. output : str Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- NoneType None """ if burnin is None: burnin = 0 if wavel_range is None: wavel_range = (0.4, 10.0) mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"] mpl.rcParams["font.family"] = "serif" plt.rc("axes", edgecolor="black", linewidth=2.2) species_db = database.Database() box = species_db.get_samples(tag) samples = box.samples if samples.ndim == 2 and random is not None: ran_index = np.random.randint(samples.shape[0], size=random) samples = samples[ran_index, ] elif samples.ndim == 3: if burnin > samples.shape[1]: raise ValueError( f"The 'burnin' value is larger than the number of steps " f"({samples.shape[1]}) that are made by the walkers.") samples = samples[:, burnin:, :] ran_walker = np.random.randint(samples.shape[0], size=random) ran_step = np.random.randint(samples.shape[1], size=random) samples = samples[ran_walker, ran_step, :] plt.figure(1, figsize=(6, 3)) gridsp = mpl.gridspec.GridSpec(1, 1) gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1) ax = plt.subplot(gridsp[0, 0]) ax.tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True, ) ax.tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True, ) ax.set_xlabel("Wavelength (µm)", fontsize=12) ax.set_ylabel("Extinction (mag)", fontsize=12) if xlim is not None: ax.set_xlim(xlim[0], xlim[1]) if ylim is not None: ax.set_ylim(ylim[0], ylim[1]) if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.22) ax.get_yaxis().set_label_coords(-0.09, 0.5) sample_wavel = np.linspace(wavel_range[0], wavel_range[1], 100) if ("lognorm_radius" in box.parameters and "lognorm_sigma" in box.parameters and "lognorm_ext" in box.parameters): cross_optical, dust_radius, dust_sigma = dust_util.interp_lognorm([], [], None) log_r_index = box.parameters.index("lognorm_radius") sigma_index = box.parameters.index("lognorm_sigma") ext_index = box.parameters.index("lognorm_ext") log_r_g = samples[:, log_r_index] sigma_g = samples[:, sigma_index] dust_ext = samples[:, ext_index] database_path = dust_util.check_dust_database() with h5py.File(database_path, "r") as h5_file: cross_section = np.asarray( h5_file["dust/lognorm/mgsio3/crystalline/cross_section"]) wavelength = np.asarray( h5_file["dust/lognorm/mgsio3/crystalline/wavelength"]) cross_interp = RegularGridInterpolator( (wavelength, dust_radius, dust_sigma), cross_section) for i in range(samples.shape[0]): cross_tmp = cross_optical["Generic/Bessell.V"](sigma_g[i], 10.0**log_r_g[i]) n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.0)) sample_cross = np.zeros(sample_wavel.shape) for j, item in enumerate(sample_wavel): sample_cross[j] = cross_interp( (item, 10.0**log_r_g[i], sigma_g[i])) sample_ext = 2.5 * np.log10(np.exp(1.0)) * sample_cross * n_grains ax.plot(sample_wavel, sample_ext, ls="-", lw=0.5, color="black", alpha=0.5) elif ("powerlaw_max" in box.parameters and "powerlaw_exp" in box.parameters and "powerlaw_ext" in box.parameters): cross_optical, dust_max, dust_exp = dust_util.interp_powerlaw([], [], None) r_max_index = box.parameters.index("powerlaw_max") exp_index = box.parameters.index("powerlaw_exp") ext_index = box.parameters.index("powerlaw_ext") r_max = samples[:, r_max_index] exponent = samples[:, exp_index] dust_ext = samples[:, ext_index] database_path = dust_util.check_dust_database() with h5py.File(database_path, "r") as h5_file: cross_section = np.asarray( h5_file["dust/powerlaw/mgsio3/crystalline/cross_section"]) wavelength = np.asarray( h5_file["dust/powerlaw/mgsio3/crystalline/wavelength"]) cross_interp = RegularGridInterpolator( (wavelength, dust_max, dust_exp), cross_section) for i in range(samples.shape[0]): cross_tmp = cross_optical["Generic/Bessell.V"](exponent[i], 10.0**r_max[i]) n_grains = dust_ext[i] / cross_tmp / 2.5 / np.log10(np.exp(1.0)) sample_cross = np.zeros(sample_wavel.shape) for j, item in enumerate(sample_wavel): sample_cross[j] = cross_interp( (item, 10.0**r_max[i], exponent[i])) sample_ext = 2.5 * np.log10(np.exp(1.0)) * sample_cross * n_grains ax.plot(sample_wavel, sample_ext, ls="-", lw=0.5, color="black", alpha=0.5) elif "ism_ext" in box.parameters: ext_index = box.parameters.index("ism_ext") ism_ext = samples[:, ext_index] if "ism_red" in box.parameters: red_index = box.parameters.index("ism_red") ism_red = samples[:, red_index] else: # Use default ISM redenning (R_V = 3.1) if ism_red was not fitted ism_red = np.full(samples.shape[0], 3.1) for i in range(samples.shape[0]): sample_ext = dust_util.ism_extinction(ism_ext[i], ism_red[i], sample_wavel) ax.plot(sample_wavel, sample_ext, ls="-", lw=0.5, color="black", alpha=0.5) else: raise ValueError( "The SamplesBox does not contain extinction parameters.") if output is None: print("Plotting extinction...", end="", flush=True) else: print(f"Plotting extinction: {output}...", end="", flush=True) print(" [DONE]") if output is None: plt.show() else: plt.savefig(output, bbox_inches="tight") plt.clf() plt.close()
def spectral_type( self, tag: str, spec_library, wavel_range: Optional[Tuple[Optional[float], Optional[float]]] = None, sptypes: Optional[List[str]] = None, av_ext: Optional[Union[List[float], np.array]] = None, rad_vel: Optional[Union[List[float], np.array]] = None, ) -> None: """ Method for finding the best fitting empirical spectra from a selected library by evaluating the goodness-of-fit statistic from Cushing et al. (2008). Parameters ---------- tag : str Database tag where for each spectrum from the spectral library the best-fit parameters will be stored. So when testing a range of values for ``av_ext`` and ``rad_vel``, only the parameters that minimize the goodness-of-fit statistic will be stored. spec_library : str Name of the spectral library ('irtf', 'spex', 'kesseli+2017', 'bonnefoy+2014'). wavel_range : tuple(float, float), None Wavelength range (um) that is used for the empirical comparison. sptypes : list(str), None List with spectral types to compare with. The list should only contains types, for example ``sptypes=['M', 'L']``. All available spectral types in the ``spec_library`` are compared with if set to ``None``. av_ext : list(float), np.array, None List of A_V extinctions for which the goodness-of-fit statistic is tested. The extinction is calculated with the empirical relation from Cardelli et al. (1989). rad_vel : list(float), np.array, None List of radial velocities (km s-1) for which the goodness-of-fit statistic is tested. Returns ------- NoneType None """ w_i = 1.0 if av_ext is None: av_ext = [0.0] if rad_vel is None: rad_vel = [0.0] h5_file = h5py.File(self.database, "r") try: h5_file[f"spectra/{spec_library}"] except KeyError: h5_file.close() species_db = database.Database() species_db.add_spectra(spec_library) h5_file = h5py.File(self.database, "r") # Read object spectra and resolution obj_spec = [] obj_res = [] for item in self.spec_name: obj_spec.append(self.object.get_spectrum()[item][0]) obj_res.append(self.object.get_spectrum()[item][3]) # Read inverted covariance matrix # obj_inv_cov = self.object.get_spectrum()[self.spec_name][2] # Create empty lists for results name_list = [] spt_list = [] gk_list = [] ck_list = [] av_list = [] rv_list = [] print_message = "" # Start looping over library spectra for i, item in enumerate(h5_file[f"spectra/{spec_library}"]): # Read spectrum spectral type from library dset = h5_file[f"spectra/{spec_library}/{item}"] if isinstance(dset.attrs["sptype"], str): item_sptype = dset.attrs["sptype"] else: # Use decode for backward compatibility item_sptype = dset.attrs["sptype"].decode("utf-8") if item_sptype == "None": continue if sptypes is None or item_sptype[0] in sptypes: # Convert HDF5 dataset into numpy array spectrum = np.asarray(dset) if wavel_range is not None: # Select subset of the spectrum if wavel_range[0] is None: indices = np.where( (spectrum[:, 0] < wavel_range[1]))[0] elif wavel_range[1] is None: indices = np.where( (spectrum[:, 0] > wavel_range[0]))[0] else: indices = np.where((spectrum[:, 0] > wavel_range[0]) & (spectrum[:, 0] < wavel_range[1]))[0] if len(indices) == 0: raise ValueError( "The selected wavelength range does not cover any " "wavelength points of the input spectrum. Please " "use a broader range as argument of 'wavel_range'." ) spectrum = spectrum[indices, ] empty_message = len(print_message) * " " print(f"\r{empty_message}", end="") print_message = f"Processing spectra... {item}" print(f"\r{print_message}", end="") # Loop over all values of A_V and RV that will be tested for av_item in av_ext: for rv_item in rad_vel: for j, spec_item in enumerate(obj_spec): # Dust extinction ism_ext = dust_util.ism_extinction( av_item, 3.1, spectrum[:, 0]) flux_scaling = 10.0**(-0.4 * ism_ext) # Shift wavelengths by RV wavel_shifted = (spectrum[:, 0] + spectrum[:, 0] * 1e3 * rv_item / constants.LIGHT) # Smooth spectrum flux_smooth = read_util.smooth_spectrum( wavel_shifted, spectrum[:, 1] * flux_scaling, spec_res=obj_res[j], force_smooth=True, ) # Interpolate library spectrum to object wavelengths interp_spec = interp1d( spectrum[:, 0], flux_smooth, kind="linear", fill_value="extrapolate", ) indices = np.where( (spec_item[:, 0] > np.amin(spectrum[:, 0])) & (spec_item[:, 0] < np.amax(spectrum[:, 0])))[0] flux_resample = interp_spec(spec_item[indices, 0]) c_numer = (w_i * spec_item[indices, 1] * flux_resample / spec_item[indices, 2]**2) c_denom = (w_i * flux_resample**2 / spec_item[indices, 2]**2) if j == 0: g_k = 0.0 c_k_spec = [] c_k = np.sum(c_numer) / np.sum(c_denom) c_k_spec.append(c_k) chi_sq = (spec_item[indices, 1] - c_k * flux_resample) / spec_item[indices, 2] g_k += np.sum(w_i * chi_sq**2) # obj_inv_cov_crop = obj_inv_cov[indices, :] # obj_inv_cov_crop = obj_inv_cov_crop[:, indices] # g_k = np.dot(spec_item[indices, 1]-c_k*flux_resample, # np.dot(obj_inv_cov_crop, # spec_item[indices, 1]-c_k*flux_resample)) # Append to the lists of results name_list.append(item) spt_list.append(item_sptype) gk_list.append(g_k) ck_list.append(c_k_spec) av_list.append(av_item) rv_list.append(rv_item) empty_message = len(print_message) * " " print(f"\r{empty_message}", end="") print("\rProcessing spectra... [DONE]") h5_file.close() name_list = np.asarray(name_list) spt_list = np.asarray(spt_list) gk_list = np.asarray(gk_list) ck_list = np.asarray(ck_list) av_list = np.asarray(av_list) rv_list = np.asarray(rv_list) sort_index = np.argsort(gk_list) name_list = name_list[sort_index] spt_list = spt_list[sort_index] gk_list = gk_list[sort_index] ck_list = ck_list[sort_index] av_list = av_list[sort_index] rv_list = rv_list[sort_index] name_select = [] spt_select = [] gk_select = [] ck_select = [] av_select = [] rv_select = [] for i, item in enumerate(name_list): if item not in name_select: name_select.append(item) spt_select.append(spt_list[i]) gk_select.append(gk_list[i]) ck_select.append(ck_list[i]) av_select.append(av_list[i]) rv_select.append(rv_list[i]) print("Best-fitting spectra:") if len(gk_select) < 10: for i, gk_item in enumerate(gk_select): print( f" {i+1:2d}. G = {gk_item:.2e} -> {name_select[i]}, {spt_select[i]}, " f"A_V = {av_select[i]:.2f}, RV = {rv_select[i]:.0f} km/s,\n" f" scalings = {ck_select[i]}") else: for i in range(10): print( f" {i+1:2d}. G = {gk_select[i]:.2e} -> {name_select[i]}, {spt_select[i]}, " f"A_V = {av_select[i]:.2f}, RV = {rv_select[i]:.0f} km/s,\n" f" scalings = {ck_select[i]}") species_db = database.Database() species_db.add_empirical( tag=tag, names=name_select, sptypes=spt_select, goodness_of_fit=gk_select, flux_scaling=ck_select, av_ext=av_select, rad_vel=rv_select, object_name=self.object_name, spec_name=self.spec_name, spec_library=spec_library, )
def lnlike_multinest(cube, n_dim: int, n_param: int) -> np.float64: """ Function for the logarithm of the likelihood, computed from the parameter cube. Parameters ---------- cube : pymultinest.run.LP_c_double Unit cube. n_dim : int Number of dimensions. n_param : int Number of parameters. Returns ------- float Log likelihood. """ param_dict = {} spec_scaling = {} err_offset = {} corr_len = {} corr_amp = {} dust_param = {} for item in self.bounds: if item[:8] == 'scaling_' and item[8:] in self.spectrum: spec_scaling[item[8:]] = cube[cube_index[item]] elif item[:6] == 'error_' and item[6:] in self.spectrum: err_offset[item[6:]] = cube[cube_index[item]] # log10(um) elif item[:9] == 'corr_len_' and item[9:] in self.spectrum: corr_len[item[9:]] = 10.**cube[cube_index[item]] # (um) elif item[:9] == 'corr_amp_' and item[9:] in self.spectrum: corr_amp[item[9:]] = cube[cube_index[item]] elif item[:8] == 'lognorm_': dust_param[item] = cube[cube_index[item]] elif item[:9] == 'powerlaw_': dust_param[item] = cube[cube_index[item]] elif item[:4] == 'ism_': dust_param[item] = cube[cube_index[item]] else: param_dict[item] = cube[cube_index[item]] if self.model == 'planck': param_dict['distance'] = self.distance[0] else: flux_scaling = (param_dict['radius']*constants.R_JUP)**2 / \ (self.distance[0]*constants.PARSEC)**2 # The scaling is applied manually because of the interpolation del param_dict['radius'] for item in self.spectrum: if item not in spec_scaling: spec_scaling[item] = 1. if item not in err_offset: err_offset[item] = None ln_like = 0. if self.model == 'planck' and self.n_planck > 1: for i in range(self.n_planck - 1): if param_dict[f'teff_{i+1}'] > param_dict[f'teff_{i}']: return -np.inf if param_dict[f'radius_{i}'] > param_dict[f'radius_{i+1}']: return -np.inf if prior is not None: for key, value in prior.items(): if key == 'mass': mass = read_util.get_mass(cube[cube_index['logg']], cube[cube_index['radius']]) ln_like += -0.5 * (mass - value[0])**2 / value[1]**2 else: ln_like += -0.5 * (cube[cube_index[key]] - value[0])**2 / value[1]**2 if 'lognorm_ext' in dust_param: cross_tmp = self.cross_sections['Generic/Bessell.V']( dust_param['lognorm_sigma'], 10.**dust_param['lognorm_radius'])[0] n_grains = dust_param[ 'lognorm_ext'] / cross_tmp / 2.5 / np.log10(np.exp(1.)) elif 'powerlaw_ext' in dust_param: cross_tmp = self.cross_sections['Generic/Bessell.V']( dust_param['powerlaw_exp'], 10.**dust_param['powerlaw_max']) n_grains = dust_param[ 'powerlaw_ext'] / cross_tmp / 2.5 / np.log10(np.exp(1.)) for i, obj_item in enumerate(self.objphot): if self.model == 'planck': readplanck = read_planck.ReadPlanck( filter_name=self.modelphot[i].filter_name) phot_flux = readplanck.get_flux( param_dict, synphot=self.modelphot[i])[0] else: phot_flux = self.modelphot[i].spectrum_interp( list(param_dict.values()))[0][0] phot_flux *= flux_scaling if 'lognorm_ext' in dust_param: cross_tmp = self.cross_sections[ self.modelphot[i].filter_name]( dust_param['lognorm_sigma'], 10.**dust_param['lognorm_radius'])[0] phot_flux *= np.exp(-cross_tmp * n_grains) elif 'powerlaw_ext' in dust_param: cross_tmp = self.cross_sections[ self.modelphot[i].filter_name]( dust_param['powerlaw_exp'], 10.**dust_param['powerlaw_max'])[0] phot_flux *= np.exp(-cross_tmp * n_grains) elif 'ism_ext' in dust_param: read_filt = read_filter.ReadFilter( self.modelphot[i].filter_name) filt_wavel = np.array([read_filt.mean_wavelength()]) ext_filt = dust_util.ism_extinction( dust_param['ism_ext'], dust_param['ism_red'], filt_wavel) phot_flux *= 10.**(-0.4 * ext_filt[0]) if obj_item.ndim == 1: ln_like += -0.5 * (obj_item[0] - phot_flux)**2 / obj_item[1]**2 else: for j in range(obj_item.shape[1]): ln_like += -0.5 * (obj_item[0, j] - phot_flux)**2 / obj_item[1, j]**2 for i, item in enumerate(self.spectrum.keys()): data_flux = spec_scaling[item] * self.spectrum[item][0][:, 1] if err_offset[item] is None: data_var = self.spectrum[item][0][:, 2]**2 else: data_var = (self.spectrum[item][0][:, 2] + 10.**err_offset[item])**2 if self.spectrum[item][2] is not None: if err_offset[item] is None: data_cov_inv = self.spectrum[item][2] else: # Ratio of the inflated and original uncertainties sigma_ratio = np.sqrt( data_var) / self.spectrum[item][0][:, 2] sigma_j, sigma_i = np.meshgrid(sigma_ratio, sigma_ratio) # Calculate the inversion of the infalted covariances data_cov_inv = np.linalg.inv(self.spectrum[item][1] * sigma_i * sigma_j) if self.model == 'planck': readplanck = read_planck.ReadPlanck( (0.9 * self.spectrum[item][0][0, 0], 1.1 * self.spectrum[item][0][-1, 0])) model_box = readplanck.get_spectrum(param_dict, 1000., smooth=True) model_flux = spectres.spectres( self.spectrum[item][0][:, 0], model_box.wavelength, model_box.flux) else: model_flux = self.modelspec[i].spectrum_interp( list(param_dict.values()))[0, :] model_flux *= flux_scaling if 'lognorm_ext' in dust_param: for j, cross_item in enumerate(self.cross_sections[item]): cross_tmp = cross_item( dust_param['lognorm_sigma'], 10.**dust_param['lognorm_radius'])[0] model_flux[j] *= np.exp(-cross_tmp * n_grains) elif 'powerlaw_ext' in dust_param: for j, cross_item in enumerate(self.cross_sections[item]): cross_tmp = cross_item( dust_param['powerlaw_exp'], 10.**dust_param['powerlaw_max'])[0] model_flux[j] *= np.exp(-cross_tmp * n_grains) elif 'ism_ext' in dust_param: ext_filt = dust_util.ism_extinction( dust_param['ism_ext'], dust_param['ism_red'], self.spectrum[item][0][:, 0]) model_flux *= 10.**(-0.4 * ext_filt) if self.spectrum[item][2] is not None: # Use the inverted covariance matrix dot_tmp = np.dot( data_flux - model_flux, np.dot(data_cov_inv, data_flux - model_flux)) ln_like += -0.5 * dot_tmp - 0.5 * np.nansum( np.log(2. * np.pi * data_var)) else: if item in self.fit_corr: # Covariance model (Wang et al. 2020) wavel = self.spectrum[item][0][:, 0] # (um) wavel_j, wavel_i = np.meshgrid(wavel, wavel) error = np.sqrt(data_var) # (W m-2 um-1) error_j, error_i = np.meshgrid(error, error) cov_matrix = corr_amp[item]**2 * error_i * error_j * \ np.exp(-(wavel_i-wavel_j)**2 / (2.*corr_len[item]**2)) + \ (1.-corr_amp[item]**2) * np.eye(wavel.shape[0])*error_i**2 dot_tmp = np.dot( data_flux - model_flux, np.dot(np.linalg.inv(cov_matrix), data_flux - model_flux)) ln_like += -0.5 * dot_tmp - 0.5 * np.nansum( np.log(2. * np.pi * data_var)) else: # Calculate the chi-square without a covariance matrix ln_like += np.nansum( -0.5 * (data_flux - model_flux)**2 / data_var - 0.5 * np.log(2. * np.pi * data_var)) return ln_like
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()