def run_mcmc(self, nwalkers: int, nsteps: int, guess: Dict[str, float], tag: str) -> None: """ Function to run the MCMC sampler. Parameters ---------- nwalkers : int Number of walkers. nsteps : int Number of steps per walker. guess : dict Guess of the scaling parameter. tag : str Database tag where the MCMC samples are stored. Returns ------- NoneType None """ print('Running MCMC...') ndim = 1 initial = np.zeros((nwalkers, ndim)) initial[:, 0] = guess['scaling'] + np.random.normal(0, 1e-1*guess['scaling'], nwalkers) if ndim > 1: for i in range(1, ndim): initial[:, i] = 1. + np.random.normal(0, 0.1, nwalkers) self.modelpar.append('scaling'+str(i)) self.bounds['scaling'+str(i)] = (0., 1e2) with Pool(processes=cpu_count()): ens_sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob, args=([self.bounds, self.modelpar, self.objphot, self.specphot])) ens_sampler.run_mcmc(initial, nsteps, progress=True) species_db = database.Database() species_db.add_samples(sampler='emcee', samples=ens_sampler.chain, ln_prob=ens_sampler.lnprobability, mean_accept=np.mean(ens_sampler.acceptance_fraction), spectrum=('calibration', self.spectrum), tag=tag, modelpar=self.modelpar, distance=None, spec_labels=None)
def check_dust_database() -> str: """ Function to check if the dust data is present in the database and add the data if needed. Returns ------- str The database path from the configuration file. """ config_file = os.path.join(os.getcwd(), 'species_config.ini') config = configparser.ConfigParser() config.read_file(open(config_file)) database_path = config['species']['database'] h5_file = h5py.File(database_path, 'r') if 'dust' not in h5_file: h5_file.close() species_db = database.Database() species_db.add_dust() h5_file = h5py.File(database_path, 'r') h5_file.close() return database_path
def get_filter(self): """ Returns ------- numpy.ndarray Filter data. """ h5_file = h5py.File(self.database, 'r') try: h5_file['filters/' + self.filter_name] except KeyError: h5_file.close() species_db = database.Database() species_db.add_filter(self.filter_name) h5_file = h5py.File(self.database, 'r') data = h5_file['filters/' + self.filter_name] data = np.asarray(data) h5_file.close() return data
def __init__(self, filter_name: str) -> None: """ Parameters ---------- filter_name : str Filter name as stored in the database. Filter names from the SVO Filter Profile Service will be automatically downloaded, stored in the database, and read from the database. Returns ------- NoneType None """ self.filter_name = filter_name config_file = os.path.join(os.getcwd(), "species_config.ini") config = configparser.ConfigParser() config.read(config_file) self.database = config["species"]["database"] h5_file = h5py.File(self.database, "r") if "filters" not in h5_file or self.filter_name not in h5_file[ "filters"]: h5_file.close() species_db = database.Database() species_db.add_filter(self.filter_name) h5_file = h5py.File(self.database, "r") h5_file.close()
def __init__(self, filter_name: str) -> None: """ Parameters ---------- filter_name : str Filter name as stored in the database. Filter names from the SVO Filter Profile Service will be automatically downloaded, stored in the database, and read from the database. Returns ------- NoneType None """ self.filter_name = filter_name config_file = os.path.join(os.getcwd(), 'species_config.ini') config = configparser.ConfigParser() config.read_file(open(config_file)) self.database = config['species']['database'] h5_file = h5py.File(self.database, 'r') if 'filters' not in h5_file or self.filter_name not in h5_file[ 'filters']: h5_file.close() species_db = database.Database() species_db.add_filter(self.filter_name) h5_file = h5py.File(self.database, 'r') h5_file.close()
def open_database(self): """ Returns ------- h5py._hl.files.File Database. """ config_file = os.path.join(os.getcwd(), 'species_config.ini') config = configparser.ConfigParser() config.read_file(open(config_file)) database_path = config['species']['database'] h5_file = h5py.File(database_path, 'r') try: h5_file['models/' + self.model] except KeyError: h5_file.close() species_db = database.Database() species_db.add_model(self.model, self.wavelength, self.teff) h5_file = h5py.File(database_path, 'r') return h5_file
def get_parallax(): species_db = database.Database() species_db.add_photometry('vlm-plx') with h5py.File(species_db.database, 'a') as hdf_file: name = np.asarray(hdf_file['photometry/vlm-plx/name']) ra_coord = np.asarray(hdf_file['photometry/vlm-plx/ra']) dec_coord = np.asarray(hdf_file['photometry/vlm-plx/dec']) distance = np.asarray(hdf_file['photometry/vlm-plx/distance']) distance_error = np.asarray( hdf_file['photometry/vlm-plx/distance_error']) simbad_id = [] print('Querying SIMBAD...', end='', flush=True) for i, item in enumerate(name): target_coord = SkyCoord(ra_coord[i], dec_coord[i], unit=(u.deg, u.deg), frame='icrs') result_table = Simbad.query_region(target_coord, radius='0d0m2s') if result_table is None: result_table = Simbad.query_region(target_coord, radius='0d0m5s') if result_table is None: result_table = Simbad.query_region(target_coord, radius='0d0m20s') if result_table is None: result_table = Simbad.query_region(target_coord, radius='0d1m0s') if item == 'HIP38939B': simbad_id.append(get_simbad('HIP38939').decode('utf-8')) else: simbad_id.append(result_table['MAIN_ID'][0].decode('utf-8')) print(' [DONE]') simbad_id = np.asarray(simbad_id) dtype = h5py.special_dtype(vlen=str) dset = hdf_file.create_dataset('photometry/vlm-plx/simbad', (np.size(simbad_id), ), dtype=dtype) dset[...] = simbad_id np.savetxt( 'parallax.dat', np.column_stack([name, simbad_id, distance, distance_error]), header='VLM-PLX name - SIMBAD name - Distance (pc) - Error (pc)', fmt='%35s, %35s, %8.2f, %8.2f')
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, 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 zero_point(self) -> np.float64: """ Internal function for calculating the zero point of the provided ``filter_name``. Returns ------- float Zero-point flux (W m-2 um-1). """ if self.wavel_range is None: transmission = read_filter.ReadFilter(self.filter_name) self.wavel_range = transmission.wavelength_range() h5_file = h5py.File(self.database, "r") try: h5_file["spectra/calibration/vega"] except KeyError: h5_file.close() species_db = database.Database() species_db.add_spectra("vega") h5_file = h5py.File(self.database, "r") readcalib = read_calibration.ReadCalibration("vega", None) calibbox = readcalib.get_spectrum() wavelength = calibbox.wavelength flux = calibbox.flux wavelength_crop = wavelength[(wavelength > self.wavel_range[0]) & (wavelength < self.wavel_range[1])] flux_crop = flux[(wavelength > self.wavel_range[0]) & (wavelength < self.wavel_range[1])] h5_file.close() return self.spectrum_to_flux(wavelength_crop, flux_crop)[0]
def check_dust_database() -> str: """ Function to check if the dust data is present in the database and add the data if needed. Returns ------- str The database path from the configuration file. """ config_file = os.path.join(os.getcwd(), "species_config.ini") config = configparser.ConfigParser() config.read(config_file) database_path = config["species"]["database"] if "dust" not in h5py.File(database_path, "r"): species_db = database.Database() species_db.add_dust() return database_path
def zero_point(self): """ Returns ------- tuple(float, float) """ if self.wl_range is None: transmission = read_filter.ReadFilter(self.filter_name) self.wl_range = transmission.wavelength_range() h5_file = h5py.File(self.database, 'r') try: h5_file['spectra/calibration/vega'] except KeyError: h5_file.close() species_db = database.Database() species_db.add_spectrum('vega') h5_file = h5py.File(self.database, 'r') readcalib = read_calibration.ReadCalibration('vega', None) calibbox = readcalib.get_spectrum() wavelength = calibbox.wavelength flux = calibbox.flux wavelength_crop = wavelength[(wavelength > self.wl_range[0]) & (wavelength < self.wl_range[1])] flux_crop = flux[(wavelength > self.wl_range[0]) & (wavelength < self.wl_range[1])] h5_file.close() return self.spectrum_to_photometry(wavelength_crop, flux_crop)
def run_multinest( self, tag: str, n_live_points: int = 1000, output: str = 'multinest/', prior: Optional[Dict[str, Tuple[float, float]]] = None) -> None: """ Function to run the ``PyMultiNest`` wrapper of the ``MultiNest`` sampler. While ``PyMultiNest`` can be installed with ``pip`` from the PyPI repository, ``MultiNest`` has to to be build manually. See the ``PyMultiNest`` documentation for details: http://johannesbuchner.github.io/PyMultiNest/install.html. Note that the library path of ``MultiNest`` should be set to the environmental variable ``LD_LIBRARY_PATH`` on a Linux machine and ``DYLD_LIBRARY_PATH`` on a Mac. Alternatively, the variable can be set before importing the ``species`` package, for example: .. code-block:: python >>> import os >>> os.environ['DYLD_LIBRARY_PATH'] = '/path/to/MultiNest/lib' >>> import species Parameters ---------- tag : str Database tag where the samples will be stored. n_live_points : int Number of live points. output : str Path that is used for the output files from MultiNest. prior : dict(str, tuple(float, float)), None Dictionary with Gaussian priors for one or multiple parameters. The prior can be set for any of the atmosphere or calibration parameters, e.g. ``prior={'teff': (1200., 100.)}``. Additionally, a prior can be set for the mass, e.g. ``prior={'mass': (13., 3.)}`` for an expected mass of 13 Mjup with an uncertainty of 3 Mjup. The parameter is not used if set to ``None``. Returns ------- NoneType None """ print('Running nested sampling...') # Create the output folder if required if not os.path.exists(output): os.mkdir(output) # Create a dictionary with the cube indices of the parameters cube_index = {} for i, item in enumerate(self.modelpar): cube_index[item] = i @typechecked def lnprior_multinest(cube, n_dim: int, n_param: int) -> None: """ Function to transform the unit cube into the parameter cube. It is not clear how to pass additional arguments to the function, therefore it is placed here and not merged with :func:`~species.analysis.fit_model.FitModel.run_mcmc`. Parameters ---------- cube : pymultinest.run.LP_c_double Unit cube. n_dim : int Number of dimensions. n_param : int Number of parameters. Returns ------- NoneType None """ for item in cube_index: # Uniform priors for all parameters cube[cube_index[item]] = self.bounds[item][0] + \ (self.bounds[item][1]-self.bounds[item][0])*cube[cube_index[item]] @typechecked def lnlike_multinest(cube, n_dim: int, n_param: int) -> np.float64: """ Function for the logarithm of the likelihood, computed from the parameter cube. Parameters ---------- cube : pymultinest.run.LP_c_double Unit cube. n_dim : int Number of dimensions. n_param : int Number of parameters. Returns ------- float Log likelihood. """ param_dict = {} spec_scaling = {} err_offset = {} corr_len = {} corr_amp = {} dust_param = {} for item in self.bounds: if item[:8] == 'scaling_' and item[8:] in self.spectrum: spec_scaling[item[8:]] = cube[cube_index[item]] elif item[:6] == 'error_' and item[6:] in self.spectrum: err_offset[item[6:]] = cube[cube_index[item]] # log10(um) elif item[:9] == 'corr_len_' and item[9:] in self.spectrum: corr_len[item[9:]] = 10.**cube[cube_index[item]] # (um) elif item[:9] == 'corr_amp_' and item[9:] in self.spectrum: corr_amp[item[9:]] = cube[cube_index[item]] elif item[:8] == 'lognorm_': dust_param[item] = cube[cube_index[item]] elif item[:9] == 'powerlaw_': dust_param[item] = cube[cube_index[item]] elif item[:4] == 'ism_': dust_param[item] = cube[cube_index[item]] else: param_dict[item] = cube[cube_index[item]] if self.model == 'planck': param_dict['distance'] = self.distance[0] else: flux_scaling = (param_dict['radius']*constants.R_JUP)**2 / \ (self.distance[0]*constants.PARSEC)**2 # The scaling is applied manually because of the interpolation del param_dict['radius'] for item in self.spectrum: if item not in spec_scaling: spec_scaling[item] = 1. if item not in err_offset: err_offset[item] = None ln_like = 0. if self.model == 'planck' and self.n_planck > 1: for i in range(self.n_planck - 1): if param_dict[f'teff_{i+1}'] > param_dict[f'teff_{i}']: return -np.inf if param_dict[f'radius_{i}'] > param_dict[f'radius_{i+1}']: return -np.inf if prior is not None: for key, value in prior.items(): if key == 'mass': mass = read_util.get_mass(cube[cube_index['logg']], cube[cube_index['radius']]) ln_like += -0.5 * (mass - value[0])**2 / value[1]**2 else: ln_like += -0.5 * (cube[cube_index[key]] - value[0])**2 / value[1]**2 if 'lognorm_ext' in dust_param: cross_tmp = self.cross_sections['Generic/Bessell.V']( dust_param['lognorm_sigma'], 10.**dust_param['lognorm_radius'])[0] n_grains = dust_param[ 'lognorm_ext'] / cross_tmp / 2.5 / np.log10(np.exp(1.)) elif 'powerlaw_ext' in dust_param: cross_tmp = self.cross_sections['Generic/Bessell.V']( dust_param['powerlaw_exp'], 10.**dust_param['powerlaw_max']) n_grains = dust_param[ 'powerlaw_ext'] / cross_tmp / 2.5 / np.log10(np.exp(1.)) for i, obj_item in enumerate(self.objphot): if self.model == 'planck': readplanck = read_planck.ReadPlanck( filter_name=self.modelphot[i].filter_name) phot_flux = readplanck.get_flux( param_dict, synphot=self.modelphot[i])[0] else: phot_flux = self.modelphot[i].spectrum_interp( list(param_dict.values()))[0][0] phot_flux *= flux_scaling if 'lognorm_ext' in dust_param: cross_tmp = self.cross_sections[ self.modelphot[i].filter_name]( dust_param['lognorm_sigma'], 10.**dust_param['lognorm_radius'])[0] phot_flux *= np.exp(-cross_tmp * n_grains) elif 'powerlaw_ext' in dust_param: cross_tmp = self.cross_sections[ self.modelphot[i].filter_name]( dust_param['powerlaw_exp'], 10.**dust_param['powerlaw_max'])[0] phot_flux *= np.exp(-cross_tmp * n_grains) elif 'ism_ext' in dust_param: read_filt = read_filter.ReadFilter( self.modelphot[i].filter_name) filt_wavel = np.array([read_filt.mean_wavelength()]) ext_filt = dust_util.ism_extinction( dust_param['ism_ext'], dust_param['ism_red'], filt_wavel) phot_flux *= 10.**(-0.4 * ext_filt[0]) if obj_item.ndim == 1: ln_like += -0.5 * (obj_item[0] - phot_flux)**2 / obj_item[1]**2 else: for j in range(obj_item.shape[1]): ln_like += -0.5 * (obj_item[0, j] - phot_flux)**2 / obj_item[1, j]**2 for i, item in enumerate(self.spectrum.keys()): data_flux = spec_scaling[item] * self.spectrum[item][0][:, 1] if err_offset[item] is None: data_var = self.spectrum[item][0][:, 2]**2 else: data_var = (self.spectrum[item][0][:, 2] + 10.**err_offset[item])**2 if self.spectrum[item][2] is not None: if err_offset[item] is None: data_cov_inv = self.spectrum[item][2] else: # Ratio of the inflated and original uncertainties sigma_ratio = np.sqrt( data_var) / self.spectrum[item][0][:, 2] sigma_j, sigma_i = np.meshgrid(sigma_ratio, sigma_ratio) # Calculate the inversion of the infalted covariances data_cov_inv = np.linalg.inv(self.spectrum[item][1] * sigma_i * sigma_j) if self.model == 'planck': readplanck = read_planck.ReadPlanck( (0.9 * self.spectrum[item][0][0, 0], 1.1 * self.spectrum[item][0][-1, 0])) model_box = readplanck.get_spectrum(param_dict, 1000., smooth=True) model_flux = spectres.spectres( self.spectrum[item][0][:, 0], model_box.wavelength, model_box.flux) else: model_flux = self.modelspec[i].spectrum_interp( list(param_dict.values()))[0, :] model_flux *= flux_scaling if 'lognorm_ext' in dust_param: for j, cross_item in enumerate(self.cross_sections[item]): cross_tmp = cross_item( dust_param['lognorm_sigma'], 10.**dust_param['lognorm_radius'])[0] model_flux[j] *= np.exp(-cross_tmp * n_grains) elif 'powerlaw_ext' in dust_param: for j, cross_item in enumerate(self.cross_sections[item]): cross_tmp = cross_item( dust_param['powerlaw_exp'], 10.**dust_param['powerlaw_max'])[0] model_flux[j] *= np.exp(-cross_tmp * n_grains) elif 'ism_ext' in dust_param: ext_filt = dust_util.ism_extinction( dust_param['ism_ext'], dust_param['ism_red'], self.spectrum[item][0][:, 0]) model_flux *= 10.**(-0.4 * ext_filt) if self.spectrum[item][2] is not None: # Use the inverted covariance matrix dot_tmp = np.dot( data_flux - model_flux, np.dot(data_cov_inv, data_flux - model_flux)) ln_like += -0.5 * dot_tmp - 0.5 * np.nansum( np.log(2. * np.pi * data_var)) else: if item in self.fit_corr: # Covariance model (Wang et al. 2020) wavel = self.spectrum[item][0][:, 0] # (um) wavel_j, wavel_i = np.meshgrid(wavel, wavel) error = np.sqrt(data_var) # (W m-2 um-1) error_j, error_i = np.meshgrid(error, error) cov_matrix = corr_amp[item]**2 * error_i * error_j * \ np.exp(-(wavel_i-wavel_j)**2 / (2.*corr_len[item]**2)) + \ (1.-corr_amp[item]**2) * np.eye(wavel.shape[0])*error_i**2 dot_tmp = np.dot( data_flux - model_flux, np.dot(np.linalg.inv(cov_matrix), data_flux - model_flux)) ln_like += -0.5 * dot_tmp - 0.5 * np.nansum( np.log(2. * np.pi * data_var)) else: # Calculate the chi-square without a covariance matrix ln_like += np.nansum( -0.5 * (data_flux - model_flux)**2 / data_var - 0.5 * np.log(2. * np.pi * data_var)) return ln_like pymultinest.run(lnlike_multinest, lnprior_multinest, len(self.modelpar), outputfiles_basename=output, resume=False, n_live_points=n_live_points) # Create the Analyzer object analyzer = pymultinest.analyse.Analyzer(len(self.modelpar), outputfiles_basename=output) # Get a dictionary with the ln(Z) and its errors, the individual modes and their parameters # quantiles of the parameter posteriors stats = analyzer.get_stats() # Nested sampling global log-evidence ln_z = stats['nested sampling global log-evidence'] ln_z_error = stats['nested sampling global log-evidence error'] print( f'Nested sampling global log-evidence: {ln_z:.2f} +/- {ln_z_error:.2f}' ) # Nested sampling global log-evidence ln_z = stats['nested importance sampling global log-evidence'] ln_z_error = stats[ 'nested importance sampling global log-evidence error'] print( f'Nested importance sampling global log-evidence: {ln_z:.2f} +/- {ln_z_error:.2f}' ) # Get the best-fit (highest likelihood) point print('Sample with the highest likelihood:') best_params = analyzer.get_best_fit() max_lnlike = best_params['log_likelihood'] print(f' - Log-likelihood = {max_lnlike:.2f}') for i, item in enumerate(best_params['parameters']): print(f' - {self.modelpar[i]} = {item:.2f}') # Get the posterior samples samples = analyzer.get_equal_weighted_posterior() spec_labels = [] for item in self.spectrum: if f'scaling_{item}' in self.bounds: spec_labels.append(f'scaling_{item}') species_db = database.Database() species_db.add_samples(sampler='multinest', samples=samples[:, :-1], ln_prob=samples[:, -1], mean_accept=None, spectrum=('model', self.model), tag=tag, modelpar=self.modelpar, distance=self.distance[0], spec_labels=spec_labels)
def get_model(self, model_param: Dict[str, float], spec_res: Optional[float] = None, wavel_resample: Optional[np.ndarray] = None, magnitude: bool = False, smooth: bool = False) -> box.ModelBox: """ Function for extracting a model spectrum by linearly interpolating the model grid. Parameters ---------- model_param : dict Dictionary with the model parameters and values. The values should be within the boundaries of the grid. The grid boundaries of the spectra in the database can be obtained with :func:`~species.read.read_model.ReadModel.get_bounds()`. spec_res : float, None Spectral resolution that is used for smoothing the spectrum with a Gaussian kernel when ``smooth=True`` and/or resampling the spectrum when ``wavel_range`` of ``FitModel`` is not ``None``. The original wavelength points are used if both ``spec_res`` and ``wavel_resample`` are set to ``None``, or if ``smooth`` is set to ``True``. wavel_resample : np.ndarray, None Wavelength points (um) to which the spectrum is resampled. In that case, ``spec_res`` can still be used for smoothing the spectrum with a Gaussian kernel. magnitude : bool Normalize the spectrum with a flux calibrated spectrum of Vega and return the magnitude instead of flux density. smooth : bool If ``True``, the spectrum is smoothed with a Gaussian kernel to the spectral resolution of ``spec_res``. This requires either a uniform spectral resolution of the input spectra (fast) or a uniform wavelength spacing of the input spectra (slow). Returns ------- species.core.box.ModelBox Box with the model spectrum. """ if smooth and spec_res is None: warnings.warn('The \'spec_res\' argument is required for smoothing the spectrum when ' '\'smooth\' is set to True.') grid_bounds = self.get_bounds() extra_param = ['radius', 'distance', 'mass', 'luminosity', 'lognorm_radius', 'lognorm_sigma', 'lognorm_ext', 'ism_ext', 'ism_red', 'powerlaw_max', 'powerlaw_exp', 'powerlaw_ext'] for key in self.get_parameters(): if key not in model_param.keys(): raise ValueError(f'The \'{key}\' parameter is required by \'{self.model}\'. ' f'The mandatory parameters are {self.get_parameters()}.') if model_param[key] < grid_bounds[key][0]: raise ValueError(f'The input value of \'{key}\' is smaller than the lower ' f'boundary of the model grid ({model_param[key]} < ' f'{grid_bounds[key][0]}).') if model_param[key] > grid_bounds[key][1]: raise ValueError(f'The input value of \'{key}\' is larger than the upper ' f'boundary of the model grid ({model_param[key]} > ' f'{grid_bounds[key][1]}).') for key in model_param.keys(): if key not in self.get_parameters() and key not in extra_param: warnings.warn(f'The \'{key}\' parameter is not required by \'{self.model}\' so ' f'the parameter will be ignored. The mandatory parameters are ' f'{self.get_parameters()}.') if 'mass' in model_param and 'radius' not in model_param: mass = 1e3 * model_param['mass'] * constants.M_JUP # (g) radius = math.sqrt(1e3 * constants.GRAVITY * mass / (10.**model_param['logg'])) # (cm) model_param['radius'] = 1e-2 * radius / constants.R_JUP # (Rjup) if self.spectrum_interp is None: self.interpolate_model() if self.wavel_range is None: wl_points = self.get_wavelengths() self.wavel_range = (wl_points[0], wl_points[-1]) parameters = [] if 'teff' in model_param: parameters.append(model_param['teff']) if 'logg' in model_param: parameters.append(model_param['logg']) if 'feh' in model_param: parameters.append(model_param['feh']) if 'co' in model_param: parameters.append(model_param['co']) if 'fsed' in model_param: parameters.append(model_param['fsed']) flux = self.spectrum_interp(parameters)[0] if 'radius' in model_param: model_param['mass'] = read_util.get_mass(model_param['logg'], model_param['radius']) if 'distance' in model_param: scaling = (model_param['radius']*constants.R_JUP)**2 / \ (model_param['distance']*constants.PARSEC)**2 flux *= scaling if smooth: flux = read_util.smooth_spectrum(wavelength=self.wl_points, flux=flux, spec_res=spec_res) if wavel_resample is not None: flux = spectres.spectres(wavel_resample, self.wl_points, flux, spec_errs=None, fill=np.nan, verbose=True) elif spec_res is not None and not smooth: index = np.where(np.isnan(flux))[0] if index.size > 0: raise ValueError('Flux values should not contains NaNs. Please make sure that ' 'the parameter values and the wavelength range are within ' 'the grid boundaries as stored in the database.') wavel_resample = read_util.create_wavelengths( (self.wl_points[0], self.wl_points[-1]), spec_res) indices = np.where((wavel_resample > self.wl_points[0]) & (wavel_resample < self.wl_points[-2]))[0] wavel_resample = wavel_resample[indices] flux = spectres.spectres(wavel_resample, self.wl_points, flux, spec_errs=None, fill=np.nan, verbose=True) if magnitude: quantity = 'magnitude' with h5py.File(self.database, 'r') as h5_file: try: h5_file['spectra/calibration/vega'] except KeyError: h5_file.close() species_db = database.Database() species_db.add_spectrum('vega') h5_file = h5py.File(self.database, 'r') readcalib = read_calibration.ReadCalibration('vega', filter_name=None) calibbox = readcalib.get_spectrum() if wavel_resample is not None: new_spec_wavs = wavel_resample else: new_spec_wavs = self.wl_points flux_vega, _ = spectres.spectres(new_spec_wavs, calibbox.wavelength, calibbox.flux, spec_errs=calibbox.error, fill=np.nan, verbose=True) flux = -2.5*np.log10(flux/flux_vega) else: quantity = 'flux' if np.isnan(np.sum(flux)): warnings.warn(f'The resampled spectrum contains {np.sum(np.isnan(flux))} NaNs, ' f'probably because the original wavelength range does not fully ' f'encompass the new wavelength range. The happened with the ' f'following parameters: {model_param}.') if wavel_resample is None: wavelength = self.wl_points else: wavelength = wavel_resample # is_finite = np.where(np.isfinite(flux))[0] # # if wavel_resample is None: # wavelength = self.wl_points[is_finite] # else: # wavelength = wavel_resample[is_finite] # # if wavelength.shape[0] == 0: # raise ValueError(f'The model spectrum is empty. Perhaps the grid could not be ' # f'interpolated at {model_param} because zeros are stored in the ' # f'database.') model_box = box.create_box(boxtype='model', model=self.model, wavelength=wavelength, flux=flux, parameters=model_param, quantity=quantity) if 'lognorm_radius' in model_param and 'lognorm_sigma' in model_param and \ 'lognorm_ext' in model_param: model_box.flux = self.apply_lognorm_ext(model_box.wavelength, model_box.flux, model_param['lognorm_radius'], model_param['lognorm_sigma'], model_param['lognorm_ext']) if 'powerlaw_max' in model_param and 'powerlaw_exp' in model_param and \ 'powerlaw_ext' in model_param: model_box.flux = self.apply_powerlaw_ext(model_box.wavelength, model_box.flux, model_param['powerlaw_max'], model_param['powerlaw_exp'], model_param['powerlaw_ext']) if 'ism_ext' in model_param and 'ism_red' in model_param: model_box.flux = self.apply_ism_ext(model_box.wavelength, model_box.flux, model_param['ism_ext'], model_param['ism_red']) if 'radius' in model_box.parameters: model_box.parameters['luminosity'] = 4. * np.pi * ( model_box.parameters['radius'] * constants.R_JUP)**2 * constants.SIGMA_SB * \ model_box.parameters['teff']**4. / constants.L_SUN # (Lsun) return model_box
def get_spectrum(self, sptypes=None, exclude_nan=True): """ Function for selecting spectra from the database. Parameters ---------- sptypes : list('str', ) Spectral types to select from a library. The spectral types should be indicated with two characters (e.g. 'M5', 'L2', 'T3'). All spectra are selected if set to None. exclude_nan : bool Exclude wavelength points for which the flux is NaN. Returns ------- species.core.box.SpectrumBox Box with the spectra. """ h5_file = h5py.File(self.database, 'r') try: h5_file[f'spectra/{self.spec_library}'] except KeyError: h5_file.close() species_db = database.Database() species_db.add_spectrum(self.spec_library, sptypes) h5_file = h5py.File(self.database, 'r') list_wavelength = [] list_flux = [] list_error = [] list_name = [] list_simbad = [] list_sptype = [] list_distance = [] for item in h5_file[f'spectra/{self.spec_library}']: data = h5_file[f'spectra/{self.spec_library}/{item}'] wavelength = data[0, :] # (um) flux = data[1, :] # (W m-2 um-1) error = data[2, :] # (W m-2 um-1) if exclude_nan: indices = np.isnan(flux) indices = np.logical_not(indices) indices = np.where(indices)[0] wavelength = wavelength[indices] flux = flux[indices] error = error[indices] if self.wavel_range is None: wl_index = np.arange(0, len(wavelength), 1) else: wl_index = (flux > 0.) & (wavelength > self.wavel_range[0]) & \ (wavelength < self.wavel_range[1]) count = np.count_nonzero(wl_index) if count > 0: index = np.where(wl_index)[0] if index[0] > 0: wl_index[index[0] - 1] = True if index[-1] < len(wl_index)-1: wl_index[index[-1] + 1] = True list_wavelength.append(wavelength[wl_index]) list_flux.append(flux[wl_index]) list_error.append(error[wl_index]) attrs = data.attrs if 'name' in attrs: list_name.append(data.attrs['name'].decode('utf-8')) else: list_name.append('') if 'simbad' in attrs: list_simbad.append(data.attrs['simbad'].decode('utf-8')) else: list_simbad.append('') if 'sptype' in attrs: list_sptype.append(data.attrs['sptype'].decode('utf-8')) else: list_sptype.append('None') if 'distance' in attrs: list_distance.append((data.attrs['distance'], data.attrs['distance_error'])) else: list_distance.append((np.nan, np.nan)) else: list_wavelength.append(np.array([])) list_flux.append(np.array([])) list_error.append(np.array([])) list_name.append('') list_simbad.append('') list_sptype.append('None') list_distance.append((np.nan, np.nan)) specbox = box.SpectrumBox() specbox.spec_library = self.spec_library specbox.wavelength = np.asarray(list_wavelength) specbox.flux = np.asarray(list_flux) specbox.error = np.asarray(list_error) specbox.name = np.asarray(list_name) specbox.simbad = np.asarray(list_simbad) specbox.sptype = np.asarray(list_sptype) specbox.distance = np.asarray(list_distance) if sptypes is not None: indices = None for item in sptypes: if indices is None: indices = np.where(np.chararray.startswith(specbox.sptype, item))[0] else: ind_tmp = np.where(np.chararray.startswith(specbox.sptype, item))[0] indices = np.append(indices, ind_tmp) specbox.wavelength = specbox.wavelength[indices] specbox.flux = specbox.flux[indices] specbox.error = specbox.error[indices] specbox.name = specbox.name[indices] specbox.simbad = specbox.simbad[indices] specbox.sptype = specbox.sptype[indices] specbox.distance = specbox.distance[indices] return specbox
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 __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 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 plot_size_distributions(tag: str, burnin: Optional[int] = None, random: Optional[int] = None, offset: Optional[Tuple[float, float]] = None, output: str = 'size_distributions.pdf') -> None: """ Function to plot random samples of the log-normal or power-law size distribution. Parameters ---------- tag : str Database tag with the samples. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. Only required after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`. random : int, None Number of randomly selected samples. All samples are used if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. output : str Output filename. Returns ------- NoneType None """ print(f'Plotting size distributions: {output}...', end='', flush=True) if burnin is None: burnin = 0 mpl.rcParams['font.serif'] = ['Bitstream Vera Serif'] mpl.rcParams['font.family'] = 'serif' plt.rc('axes', edgecolor='black', linewidth=2.2) species_db = database.Database() box = species_db.get_samples(tag) if 'lognorm_radius' not in box.parameters and 'powerlaw_max' not in box.parameters: raise ValueError( 'The SamplesBox does not contain extinction parameter for a log-normal ' 'or power-law size distribution.') samples = box.samples if samples.ndim == 2 and random is not None: ran_index = np.random.randint(samples.shape[0], size=random) samples = samples[ran_index, ] elif samples.ndim == 3: if burnin > samples.shape[1]: raise ValueError( f'The \'burnin\' value is larger than the number of steps ' f'({samples.shape[1]}) that are made by the walkers.') samples = samples[:, burnin:, :] ran_walker = np.random.randint(samples.shape[0], size=random) ran_step = np.random.randint(samples.shape[1], size=random) samples = samples[ran_walker, ran_step, :] if 'lognorm_radius' in box.parameters: log_r_index = box.parameters.index('lognorm_radius') sigma_index = box.parameters.index('lognorm_sigma') log_r_g = samples[:, log_r_index] sigma_g = samples[:, sigma_index] if 'powerlaw_max' in box.parameters: r_max_index = box.parameters.index('powerlaw_max') exponent_index = box.parameters.index('powerlaw_exp') r_max = samples[:, r_max_index] exponent = samples[:, exponent_index] plt.figure(1, figsize=(6, 3)) gridsp = mpl.gridspec.GridSpec(1, 1) gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1) ax = plt.subplot(gridsp[0, 0]) ax.tick_params(axis='both', which='major', colors='black', labelcolor='black', direction='in', width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True) ax.tick_params(axis='both', which='minor', colors='black', labelcolor='black', direction='in', width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True) ax.set_xlabel('Grain size (µm)', fontsize=12) ax.set_ylabel('dn/dr', fontsize=12) ax.set_xscale('log') if 'powerlaw_max' in box.parameters: ax.set_yscale('log') if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.22) ax.get_yaxis().set_label_coords(-0.09, 0.5) for i in range(samples.shape[0]): if 'lognorm_radius' in box.parameters: dn_dr, _, radii = dust_util.log_normal_distribution( 10.**log_r_g[i], sigma_g[i], 1000) elif 'powerlaw_max' in box.parameters: dn_dr, _, radii = dust_util.power_law_distribution( exponent[i], 1e-3, 10.**r_max[i], 1000) ax.plot(radii, dn_dr, ls='-', lw=0.5, color='black', alpha=0.5) plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight') plt.clf() plt.close() print(' [DONE]')
def plot_posterior(tag: str, burnin: Optional[int] = None, title: Optional[str] = None, offset: Optional[Tuple[float, float]] = None, title_fmt: Union[str, List[str]] = '.2f', limits: Optional[List[Tuple[float, float]]] = None, max_posterior: bool = False, inc_luminosity: bool = False, inc_mass: bool = False, output: str = 'posterior.pdf') -> None: """ Function to plot the posterior distribution. Parameters ---------- tag : str Database tag with the samples. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. title : str, None Plot title. No title is shown if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. title_fmt : str, list(str) Format of the titles above the 1D distributions. Either a single string, which will be used for all parameters, or a list with the title format for each parameter separately (in the order as shown in the corner plot). limits : list(tuple(float, float), ), None Axis limits of all parameters. Automatically set if set to ``None``. max_posterior : bool Plot the position of the sample with the maximum posterior probability. inc_luminosity : bool Include the log10 of the luminosity in the posterior plot as calculated from the effective temperature and radius. inc_mass : bool Include the mass in the posterior plot as calculated from the surface gravity and radius. output : str Output filename. Returns ------- NoneType None """ mpl.rcParams['font.serif'] = ['Bitstream Vera Serif'] mpl.rcParams['font.family'] = 'serif' plt.rc('axes', edgecolor='black', linewidth=2.2) if burnin is None: burnin = 0 species_db = database.Database() box = species_db.get_samples(tag, burnin=burnin) print('Median sample:') for key, value in box.median_sample.items(): print(f' - {key} = {value:.2f}') samples = box.samples ndim = samples.shape[-1] if box.prob_sample is not None: par_val = tuple(box.prob_sample.values()) print('Maximum posterior sample:') for key, value in box.prob_sample.items(): print(f' - {key} = {value:.2f}') print(f'Plotting the posterior: {output}...', end='', flush=True) if inc_luminosity: if 'teff' in box.parameters and 'radius' in box.parameters: teff_index = np.argwhere(np.array(box.parameters) == 'teff')[0] radius_index = np.argwhere(np.array(box.parameters) == 'radius')[0] luminosity = 4. * np.pi * (samples[..., radius_index]*constants.R_JUP)**2 * \ constants.SIGMA_SB * samples[..., teff_index]**4. / constants.L_SUN samples = np.append(samples, np.log10(luminosity), axis=-1) box.parameters.append('luminosity') ndim += 1 elif 'teff_0' in box.parameters and 'radius_0' in box.parameters: luminosity = 0. for i in range(100): teff_index = np.argwhere( np.array(box.parameters) == f'teff_{i}') radius_index = np.argwhere( np.array(box.parameters) == f'radius_{i}') if len(teff_index) > 0 and len(radius_index) > 0: luminosity += 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \ * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN else: break samples = np.append(samples, np.log10(luminosity), axis=-1) box.parameters.append('luminosity') ndim += 1 # teff_index = np.argwhere(np.array(box.parameters) == 'teff_0') # radius_index = np.argwhere(np.array(box.parameters) == 'radius_0') # # luminosity_0 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN # # samples = np.append(samples, np.log10(luminosity_0), axis=-1) # box.parameters.append('luminosity_0') # ndim += 1 # # teff_index = np.argwhere(np.array(box.parameters) == 'teff_1') # radius_index = np.argwhere(np.array(box.parameters) == 'radius_1') # # luminosity_1 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN # # samples = np.append(samples, np.log10(luminosity_1), axis=-1) # box.parameters.append('luminosity_1') # ndim += 1 # # teff_index_0 = np.argwhere(np.array(box.parameters) == 'teff_0') # radius_index_0 = np.argwhere(np.array(box.parameters) == 'radius_0') # # teff_index_1 = np.argwhere(np.array(box.parameters) == 'teff_1') # radius_index_1 = np.argwhere(np.array(box.parameters) == 'radius_1') # # luminosity_0 = 4. * np.pi * (samples[..., radius_index_0[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index_0[0]]**4. / constants.L_SUN # # luminosity_1 = 4. * np.pi * (samples[..., radius_index_1[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index_1[0]]**4. / constants.L_SUN # # samples = np.append(samples, np.log10(luminosity_0/luminosity_1), axis=-1) # box.parameters.append('luminosity_ratio') # ndim += 1 # r_tmp = samples[..., radius_index_0[0]]*constants.R_JUP # lum_diff = (luminosity_1*constants.L_SUN-luminosity_0*constants.L_SUN) # # m_mdot = (3600.*24.*365.25)*lum_diff*r_tmp/constants.GRAVITY/constants.M_JUP**2 # # samples = np.append(samples, m_mdot, axis=-1) # box.parameters.append('m_mdot') # ndim += 1 if inc_mass: if 'logg' in box.parameters and 'radius' in box.parameters: logg_index = np.argwhere(np.array(box.parameters) == 'logg')[0] radius_index = np.argwhere(np.array(box.parameters) == 'radius')[0] mass_samples = read_util.get_mass(samples[..., logg_index], samples[..., radius_index]) samples = np.append(samples, mass_samples, axis=-1) box.parameters.append('mass') ndim += 1 else: warnings.warn( 'Samples with the log(g) and radius are required for \'inc_mass=True\'.' ) if isinstance(title_fmt, list) and len(title_fmt) != ndim: raise ValueError( f'The number of items in the list of \'title_fmt\' ({len(title_fmt)}) is ' f'not equal to the number of dimensions of the samples ({ndim}).') labels = plot_util.update_labels(box.parameters) # Check if parameter values were fixed index_sel = [] index_del = [] # Use only last axis for parameter dimensions for i in range(ndim): if np.amin(samples[..., i]) == np.amax(samples[..., i]): index_del.append(i) else: index_sel.append(i) samples = samples[..., index_sel] for i in range(len(index_del) - 1, -1, -1): del labels[index_del[i]] ndim -= len(index_del) samples = samples.reshape((-1, ndim)) hist_titles = [] for i, item in enumerate(labels): unit_start = item.find('(') if unit_start == -1: param_label = item unit_label = None else: param_label = item[:unit_start] # Remove parenthesis from the units unit_label = item[unit_start + 1:-1] q_16, q_50, q_84 = corner.quantile(samples[:, i], [0.16, 0.5, 0.84]) q_minus, q_plus = q_50 - q_16, q_84 - q_50 if isinstance(title_fmt, str): fmt = '{{0:{0}}}'.format(title_fmt).format elif isinstance(title_fmt, list): fmt = '{{0:{0}}}'.format(title_fmt[i]).format best_fit = r'${{{0}}}_{{-{1}}}^{{+{2}}}$' best_fit = best_fit.format(fmt(q_50), fmt(q_minus), fmt(q_plus)) if unit_label is None: hist_title = f'{param_label} = {best_fit}' else: hist_title = f'{param_label} = {best_fit} {unit_label}' hist_titles.append(hist_title) fig = corner.corner(samples, quantiles=[0.16, 0.5, 0.84], labels=labels, label_kwargs={'fontsize': 13}, titles=hist_titles, show_titles=True, title_fmt=None, title_kwargs={'fontsize': 12}) axes = np.array(fig.axes).reshape((ndim, ndim)) for i in range(ndim): for j in range(ndim): if i >= j: ax = axes[i, j] ax.xaxis.set_major_formatter(ScalarFormatter(useOffset=False)) ax.yaxis.set_major_formatter(ScalarFormatter(useOffset=False)) labelleft = j == 0 and i != 0 labelbottom = i == ndim - 1 ax.tick_params(axis='both', which='major', colors='black', labelcolor='black', direction='in', width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelleft=labelleft, labelbottom=labelbottom, labelright=False, labeltop=False) ax.tick_params(axis='both', which='minor', colors='black', labelcolor='black', direction='in', width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelleft=labelleft, labelbottom=labelbottom, labelright=False, labeltop=False) if limits is not None: ax.set_xlim(limits[j]) if max_posterior: ax.axvline(par_val[j], color='tomato') if i > j: if max_posterior: ax.axhline(par_val[i], color='tomato') ax.plot(par_val[j], par_val[i], 's', color='tomato') if limits is not None: ax.set_ylim(limits[i]) if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.26) ax.get_yaxis().set_label_coords(-0.27, 0.5) if title: fig.suptitle(title, y=1.02, fontsize=16) plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight') plt.clf() plt.close() print(' [DONE]')
def plot_walkers(tag: str, nsteps: Optional[int] = None, offset: Optional[Tuple[float, float]] = None, output: str = 'walkers.pdf') -> None: """ Function to plot the step history of the walkers. Parameters ---------- tag : str Database tag with the samples. nsteps : int, None Number of steps that are plotted. All steps are plotted if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. output : str Output filename. Returns ------- NoneType None """ print(f'Plotting walkers: {output}...', end='', flush=True) mpl.rcParams['font.serif'] = ['Bitstream Vera Serif'] mpl.rcParams['font.family'] = 'serif' plt.rc('axes', edgecolor='black', linewidth=2.2) species_db = database.Database() box = species_db.get_samples(tag) samples = box.samples labels = plot_util.update_labels(box.parameters) if samples.ndim == 2: raise ValueError( f'The samples of \'{tag}\' have only 2 dimensions whereas 3 are required ' f'for plotting the walkers. The plot_walkers function can only be ' f'used after running the MCMC with run_mcmc and not after running ' f'MultiNest with run_multinest.') ndim = samples.shape[-1] plt.figure(1, figsize=(6, ndim * 1.5)) gridsp = mpl.gridspec.GridSpec(ndim, 1) gridsp.update(wspace=0, hspace=0.1, left=0, right=1, bottom=0, top=1) for i in range(ndim): ax = plt.subplot(gridsp[i, 0]) if i == ndim - 1: ax.tick_params(axis='both', which='major', colors='black', labelcolor='black', direction='in', width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True) ax.tick_params(axis='both', which='minor', colors='black', labelcolor='black', direction='in', width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True) else: ax.tick_params(axis='both', which='major', colors='black', labelcolor='black', direction='in', width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=False) ax.tick_params(axis='both', which='minor', colors='black', labelcolor='black', direction='in', width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=False) if i == ndim - 1: ax.set_xlabel('Step number', fontsize=10) else: ax.set_xlabel('', fontsize=10) ax.set_ylabel(labels[i], fontsize=10) if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.22) ax.get_yaxis().set_label_coords(-0.09, 0.5) if nsteps is not None: ax.set_xlim(0, nsteps) for j in range(samples.shape[0]): ax.plot(samples[j, :, i], ls='-', lw=0.5, color='black', alpha=0.5) plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight') plt.clf() plt.close() print(' [DONE]')
def plot_size_distributions( tag: str, burnin: Optional[int] = None, random: Optional[int] = None, offset: Optional[Tuple[float, float]] = None, output: Optional[str] = "size_distributions.pdf", ) -> None: """ Function to plot random samples of the log-normal or power-law size distributions. Parameters ---------- tag : str Database tag with the samples. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. Only required after running MCMC with :func:`~species.analysis.fit_model.FitModel.run_mcmc`. random : int, None Number of randomly selected samples. All samples are used if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. output : str Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- NoneType None """ if output is None: print("Plotting size distributions...", end="", flush=True) else: print(f"Plotting size distributions: {output}...", end="", flush=True) if burnin is None: burnin = 0 mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"] mpl.rcParams["font.family"] = "serif" plt.rc("axes", edgecolor="black", linewidth=2.2) species_db = database.Database() box = species_db.get_samples(tag) if "lognorm_radius" not in box.parameters and "powerlaw_max" not in box.parameters: raise ValueError( "The SamplesBox does not contain extinction parameter for a log-normal " "or power-law size distribution.") samples = box.samples if samples.ndim == 2 and random is not None: ran_index = np.random.randint(samples.shape[0], size=random) samples = samples[ran_index, ] elif samples.ndim == 3: if burnin > samples.shape[1]: raise ValueError( f"The 'burnin' value is larger than the number of steps " f"({samples.shape[1]}) that are made by the walkers.") samples = samples[:, burnin:, :] ran_walker = np.random.randint(samples.shape[0], size=random) ran_step = np.random.randint(samples.shape[1], size=random) samples = samples[ran_walker, ran_step, :] if "lognorm_radius" in box.parameters: log_r_index = box.parameters.index("lognorm_radius") sigma_index = box.parameters.index("lognorm_sigma") log_r_g = samples[:, log_r_index] sigma_g = samples[:, sigma_index] if "powerlaw_max" in box.parameters: r_max_index = box.parameters.index("powerlaw_max") exponent_index = box.parameters.index("powerlaw_exp") r_max = samples[:, r_max_index] exponent = samples[:, exponent_index] plt.figure(1, figsize=(6, 3)) gridsp = mpl.gridspec.GridSpec(1, 1) gridsp.update(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1) ax = plt.subplot(gridsp[0, 0]) ax.tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True, ) ax.tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True, ) ax.set_xlabel("Grain size (µm)", fontsize=12) ax.set_ylabel("dn/dr", fontsize=12) ax.set_xscale("log") if "powerlaw_max" in box.parameters: ax.set_yscale("log") if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.22) ax.get_yaxis().set_label_coords(-0.09, 0.5) for i in range(samples.shape[0]): if "lognorm_radius" in box.parameters: dn_grains, r_width, radii = dust_util.log_normal_distribution( 10.0**log_r_g[i], sigma_g[i], 1000) # Exclude radii smaller than 1 nm indices = np.argwhere(radii >= 1e-3) dn_grains = dn_grains[indices] r_width = r_width[indices] radii = radii[indices] elif "powerlaw_max" in box.parameters: dn_grains, r_width, radii = dust_util.power_law_distribution( exponent[i], 1e-3, 10.0**r_max[i], 1000) ax.plot(radii, dn_grains / r_width, ls="-", lw=0.5, color="black", alpha=0.5) print(" [DONE]") if output is None: plt.show() else: plt.savefig(output, bbox_inches="tight") plt.clf() plt.close()
def run_mcmc(self, nwalkers, nsteps, guess, tag, prior=None): """ Function to run the MCMC sampler. Parameters ---------- nwalkers : int Number of walkers. nsteps : int Number of steps per walker. guess : dict Guess for the parameter values. Random values between the boundary values are used if set to None. tag : str Database tag where the MCMC samples are stored. prior : tuple(str, float, float) Gaussian prior on one of the parameters. Currently only possible for the mass, e.g. ('mass', 13., 3.) for an expected mass of 13 Mjup with an uncertainty of 3 Mjup. Not used if set to None. Returns ------- NoneType None """ global MIN_CHISQ global MIN_PARAM sigma = {'teff': 5., 'logg': 0.01, 'feh': 0.01, 'radius': 0.01} sys.stdout.write('Running MCMC...') sys.stdout.flush() ndim = len(self.bounds) initial = np.zeros((nwalkers, ndim)) for i, item in enumerate(self.modelpar): if guess[item] is not None: initial[:, i] = guess[item] + np.random.normal(0, sigma[item], nwalkers) else: initial[:, i] = np.random.uniform(low=self.bounds[item][0], high=self.bounds[item][1], size=nwalkers) sampler = emcee.EnsembleSampler(nwalkers=nwalkers, dim=ndim, lnpostfn=lnprob, a=2., args=([self.bounds, self.modelpar, self.modelphot, self.objphot, self.synphot, self.distance, prior, self.spectrum, self.instrument, self.modelspec])) progbar = progress.bar.Bar('\rRunning MCMC...', max=nsteps, suffix='%(percent)d%%') for i, _ in enumerate(sampler.sample(initial, iterations=nsteps)): progbar.next() progbar.finish() species_db = database.Database() species_db.add_samples(sampler=sampler, spectrum=('model', self.model), tag=tag, chisquare=(MIN_CHISQ, MIN_PARAM), modelpar=self.modelpar, distance=self.distance)
def plot_mag_posterior( tag: str, filter_name: str, burnin: int = None, xlim: Tuple[float, float] = None, output: Optional[str] = "mag_posterior.pdf", ) -> np.ndarray: """ Function to plot the posterior distribution of the synthetic magnitudes. The posterior samples are also returned. Parameters ---------- tag : str Database tag with the posterior samples. filter_name : str Filter name. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. xlim : tuple(float, float), None Axis limits. Automatically set if set to ``None``. output : str Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- np.ndarray Array with the posterior samples of the magnitude. """ mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"] mpl.rcParams["font.family"] = "serif" plt.rc("axes", edgecolor="black", linewidth=2.2) species_db = database.Database() samples = species_db.get_mcmc_photometry(tag, filter_name, burnin) if output is None: print("Plotting photometry samples...", end="", flush=True) else: print(f"Plotting photometry samples: {output}...", end="", flush=True) fig = corner.corner( samples, labels=["Magnitude"], quantiles=[0.16, 0.5, 0.84], label_kwargs={"fontsize": 13.0}, show_titles=True, title_kwargs={"fontsize": 12.0}, title_fmt=".2f", ) axes = np.array(fig.axes).reshape((1, 1)) ax = axes[0, 0] ax.tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, ) ax.tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, ) if xlim is not None: ax.set_xlim(xlim) ax.get_xaxis().set_label_coords(0.5, -0.26) print(" [DONE]") if output is None: plt.show() else: plt.savefig(output, bbox_inches="tight") plt.clf() plt.close() return samples
def compare_model( self, tag: str, model: str, av_points: Optional[Union[List[float], np.array]] = None, fix_logg: Optional[float] = None, scale_spec: Optional[List[str]] = None, weights: bool = True, inc_phot: Optional[List[str]] = None, ) -> None: """ Method for finding the best fitting spectrum from a grid of atmospheric model spectra by evaluating the goodness-of-fit statistic from Cushing et al. (2008). Currently, this method only supports model grids with only :math:`T_\\mathrm{eff}` and :math:`\\log(g)` as free parameters (e.g. BT-Settl). Please create an issue on Github if support for models with more than two parameters is required. Parameters ---------- tag : str Database tag where for each spectrum from the spectral library the best-fit parameters will be stored. So when testing a range of values for ``av_ext`` and ``rad_vel``, only the parameters that minimize the goodness-of-fit statistic will be stored. model : str Name of the atmospheric model grid with synthetic spectra. av_points : list(float), np.array, None List of :math:`A_V` extinction values for which the goodness-of-fit statistic will be tested. The extinction is calculated with the relation from Cardelli et al. (1989). fix_logg : float, None Fix the value of :math:`\\log(g)`, for example if estimated from gravity-sensitive spectral features. Typically, :math:`\\log(g)` can not be accurately determined when comparing the spectra over a broad wavelength range. scale_spec : list(str), None List with names of observed spectra to which a flux scaling is applied to best match the spectral templates. weights : bool Apply a weighting based on the widths of the wavelengths bins. inc_phot : list(str), None Filter names of the photometry to include in the comparison. Photometry points are weighted by the FWHM of the filter profile. No photometric fluxes will be used if the argument is set to ``None``. Returns ------- NoneType None """ w_i = {} for spec_item in self.spec_name: obj_wavel = self.object.get_spectrum()[spec_item][0][:, 0] diff = (np.diff(obj_wavel)[1:] + np.diff(obj_wavel)[:-1]) / 2.0 diff = np.insert(diff, 0, diff[0]) diff = np.append(diff, diff[-1]) if weights: w_i[spec_item] = diff else: w_i[spec_item] = np.ones(obj_wavel.shape[0]) if inc_phot is None: inc_phot = [] if scale_spec is None: scale_spec = [] phot_wavel = {} for phot_item in inc_phot: read_filt = read_filter.ReadFilter(phot_item) w_i[phot_item] = read_filt.filter_fwhm() phot_wavel[phot_item] = read_filt.mean_wavelength() if av_points is None: av_points = np.array([0.0]) elif isinstance(av_points, list): av_points = np.array(av_points) readmodel = read_model.ReadModel(model) model_param = readmodel.get_parameters() grid_points = readmodel.get_points() coord_points = [] for key, value in grid_points.items(): if key == "logg" and fix_logg is not None: if fix_logg in value: coord_points.append(np.array([fix_logg])) else: raise ValueError( f"The argument of 'fix_logg' ({fix_logg}) is not found " f"in the parameter grid of the model spectra. The following " f"values of log(g) are available: {value}") else: coord_points.append(value) if av_points is not None: model_param.append("ism_ext") coord_points.append(av_points) grid_shape = [] for item in coord_points: grid_shape.append(len(item)) fit_stat = np.zeros(grid_shape) flux_scaling = np.zeros(grid_shape) if len(scale_spec) == 0: extra_scaling = None else: grid_shape.append(len(scale_spec)) extra_scaling = np.zeros(grid_shape) count = 1 if len(coord_points) == 3: n_iter = len(coord_points[0]) * len(coord_points[1]) * len( coord_points[2]) for i, item_i in enumerate(coord_points[0]): for j, item_j in enumerate(coord_points[1]): for k, item_k in enumerate(coord_points[2]): print( f"\rProcessing model spectrum {count}/{n_iter}...", end="") model_spec = {} model_phot = {} for spec_item in self.spec_name: obj_spec = self.object.get_spectrum()[spec_item][0] obj_res = self.object.get_spectrum()[spec_item][3] param_dict = { model_param[0]: item_i, model_param[1]: item_j, model_param[2]: item_k, } wavel_range = (0.9 * obj_spec[0, 0], 1.1 * obj_spec[-1, 0]) readmodel = read_model.ReadModel( model, wavel_range=wavel_range) model_box = readmodel.get_data( param_dict, spec_res=obj_res, wavel_resample=obj_spec[:, 0], ) model_spec[spec_item] = model_box.flux for phot_item in inc_phot: readmodel = read_model.ReadModel( model, filter_name=phot_item) model_phot[phot_item] = readmodel.get_flux( param_dict)[0] def g_fit(x, scaling): g_stat = 0.0 for spec_item in self.spec_name: obs_spec = self.object.get_spectrum( )[spec_item][0] if spec_item in scale_spec: spec_idx = scale_spec.index(spec_item) c_numer = (w_i[spec_item] * obs_spec[:, 1] * model_spec[spec_item] / obs_spec[:, 2]**2) c_denom = (w_i[spec_item] * model_spec[spec_item]**2 / obs_spec[:, 2]**2) extra_scaling[i, j, k, spec_idx] = np.sum( c_numer) / np.sum(c_denom) g_stat += np.sum( w_i[spec_item] * (obs_spec[:, 1] - extra_scaling[i, j, k, spec_idx] * model_spec[spec_item])**2 / obs_spec[:, 2]**2) else: g_stat += np.sum( w_i[spec_item] * (obs_spec[:, 1] - scaling * model_spec[spec_item])**2 / obs_spec[:, 2]**2) for phot_item in inc_phot: obs_phot = self.object.get_photometry( phot_item) g_stat += ( w_i[phot_item] * (obs_phot[2] - scaling * model_phot[phot_item])**2 / obs_phot[3]**2) return g_stat popt, _ = curve_fit(g_fit, xdata=[0.0], ydata=[0.0]) scaling = popt[0] flux_scaling[i, j, k] = scaling fit_stat[i, j, k] = g_fit(0.0, scaling) count += 1 print(" [DONE]") species_db = database.Database() species_db.add_comparison( tag=tag, goodness_of_fit=fit_stat, flux_scaling=flux_scaling, model_param=model_param, coord_points=coord_points, object_name=self.object_name, spec_name=self.spec_name, model=model, scale_spec=scale_spec, extra_scaling=extra_scaling, )
def plot_posterior( tag: str, burnin: Optional[int] = None, title: Optional[str] = None, offset: Optional[Tuple[float, float]] = None, title_fmt: Union[str, List[str]] = ".2f", limits: Optional[List[Tuple[float, float]]] = None, max_prob: bool = False, vmr: bool = False, inc_luminosity: bool = False, inc_mass: bool = False, inc_pt_param: bool = False, inc_loglike: bool = False, output: Optional[str] = "posterior.pdf", ) -> None: """ Function to plot the posterior distribution of the fitted parameters. Parameters ---------- tag : str Database tag with the samples. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. title : str, None Plot title. No title is shown if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. title_fmt : str, list(str) Format of the titles above the 1D distributions. Either a single string, which will be used for all parameters, or a list with the title format for each parameter separately (in the order as shown in the corner plot). limits : list(tuple(float, float), ), None Axis limits of all parameters. Automatically set if set to ``None``. max_prob : bool Plot the position of the sample with the maximum posterior probability. vmr : bool Plot the volume mixing ratios (i.e. number fractions) instead of the mass fractions of the retrieved species with :class:`~species.analysis.retrieval.AtmosphericRetrieval`. inc_luminosity : bool Include the log10 of the luminosity in the posterior plot as calculated from the effective temperature and radius. inc_mass : bool Include the mass in the posterior plot as calculated from the surface gravity and radius. inc_pt_param : bool Include the parameters of the pressure-temperature profile. Only used if the ``tag`` contains samples obtained with :class:`~species.analysis.retrieval.AtmosphericRetrieval`. inc_loglike : bool Include the log10 of the likelihood as additional parameter in the corner plot. output : str Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- NoneType None """ mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"] mpl.rcParams["font.family"] = "serif" plt.rc("axes", edgecolor="black", linewidth=2.2) if burnin is None: burnin = 0 species_db = database.Database() box = species_db.get_samples(tag, burnin=burnin) samples = box.samples # index_sel = [0, 1, 8, 9, 14] # samples = samples[:, index_sel] # # for i in range(13, 9, -1): # del box.parameters[i] # # del box.parameters[2] # del box.parameters[2] # del box.parameters[2] # del box.parameters[2] # del box.parameters[2] # del box.parameters[2] ndim = len(box.parameters) if not inc_pt_param and box.spectrum == "petitradtrans": pt_param = ["tint", "t1", "t2", "t3", "alpha", "log_delta"] index_del = [] item_del = [] for i in range(100): pt_item = f"t{i}" if pt_item in box.parameters: param_index = np.argwhere( np.array(box.parameters) == pt_item)[0] index_del.append(param_index) item_del.append(pt_item) else: break for item in pt_param: if item in box.parameters and item not in item_del: param_index = np.argwhere(np.array(box.parameters) == item)[0] index_del.append(param_index) item_del.append(item) samples = np.delete(samples, index_del, axis=1) ndim -= len(index_del) for item in item_del: box.parameters.remove(item) if box.spectrum == "petitradtrans" and box.attributes[ "chemistry"] == "free": box.parameters.append("c_h_ratio") box.parameters.append("o_h_ratio") box.parameters.append("c_o_ratio") ndim += 3 abund_index = {} for i, item in enumerate(box.parameters): if item == "CH4": abund_index["CH4"] = i elif item == "CO": abund_index["CO"] = i elif item == "CO_all_iso": abund_index["CO_all_iso"] = i elif item == "CO_all_iso_HITEMP": abund_index["CO_all_iso_HITEMP"] = i elif item == "CO2": abund_index["CO2"] = i elif item == "FeH": abund_index["FeH"] = i elif item == "H2O": abund_index["H2O"] = i elif item == "H2O_HITEMP": abund_index["H2O_HITEMP"] = i elif item == "H2S": abund_index["H2S"] = i elif item == "Na": abund_index["Na"] = i elif item == "NH3": abund_index["NH3"] = i elif item == "K": abund_index["K"] = i elif item == "PH3": abund_index["PH3"] = i elif item == "TiO": abund_index["TiO"] = i elif item == "TiO_all_Exomol": abund_index["TiO_all_Exomol"] = i elif item == "VO": abund_index["VO"] = i elif item == "VO_Plez": abund_index["VO_Plez"] = i c_h_ratio = np.zeros(samples.shape[0]) o_h_ratio = np.zeros(samples.shape[0]) c_o_ratio = np.zeros(samples.shape[0]) for i, item in enumerate(samples): abund = {} if "CH4" in box.parameters: abund["CH4"] = item[abund_index["CH4"]] if "CO" in box.parameters: abund["CO"] = item[abund_index["CO"]] if "CO_all_iso" in box.parameters: abund["CO_all_iso"] = item[abund_index["CO"]] if "CO_all_iso_HITEMP" in box.parameters: abund["CO_all_iso_HITEMP"] = item[ abund_index["CO_all_iso_HITEMP"]] if "CO2" in box.parameters: abund["CO2"] = item[abund_index["CO2"]] if "FeH" in box.parameters: abund["FeH"] = item[abund_index["FeH"]] if "H2O" in box.parameters: abund["H2O"] = item[abund_index["H2O"]] if "H2O_HITEMP" in box.parameters: abund["H2O_HITEMP"] = item[abund_index["H2O_HITEMP"]] if "H2S" in box.parameters: abund["H2S"] = item[abund_index["H2S"]] if "Na" in box.parameters: abund["Na"] = item[abund_index["Na"]] if "K" in box.parameters: abund["K"] = item[abund_index["K"]] if "NH3" in box.parameters: abund["NH3"] = item[abund_index["NH3"]] if "PH3" in box.parameters: abund["PH3"] = item[abund_index["PH3"]] if "TiO" in box.parameters: abund["TiO"] = item[abund_index["TiO"]] if "TiO_all_Exomol" in box.parameters: abund["TiO_all_Exomol"] = item[abund_index["TiO_all_Exomol"]] if "VO" in box.parameters: abund["VO"] = item[abund_index["VO"]] if "VO_Plez" in box.parameters: abund["VO_Plez"] = item[abund_index["VO_Plez"]] c_h_ratio[i], o_h_ratio[i], c_o_ratio[ i] = retrieval_util.calc_metal_ratio(abund) if (vmr and box.spectrum == "petitradtrans" and box.attributes["chemistry"] == "free"): print("Changing mass fractions to number fractions...", end="", flush=True) # Get all available line species line_species = retrieval_util.get_line_species() # Get the atomic and molecular masses masses = retrieval_util.atomic_masses() # Create array for the updated samples updated_samples = np.zeros(samples.shape) for i, samples_item in enumerate(samples): # Initiate a dictionary for the log10 mass fraction of the metals log_x_abund = {} for param_item in box.parameters: if param_item in line_species: # Get the index of the parameter param_index = box.parameters.index(param_item) # Store log10 mass fraction in the dictionary log_x_abund[param_item] = samples_item[param_index] # Create a dictionary with all mass fractions, including H2 and He x_abund = retrieval_util.mass_fractions(log_x_abund) # Calculate the mean molecular weight from the input mass fractions mmw = retrieval_util.mean_molecular_weight(x_abund) for param_item in box.parameters: if param_item in line_species: # Get the index of the parameter param_index = box.parameters.index(param_item) # Overwrite the sample with the log10 number fraction samples_item[param_index] = np.log10( 10.0**samples_item[param_index] * mmw / masses[param_item]) # Store the updated sample to the array updated_samples[i, ] = samples_item # Overwrite the samples in the SamplesBox box.samples = updated_samples print(" [DONE]") print("Median sample:") for key, value in box.median_sample.items(): print(f" - {key} = {value:.2e}") if "gauss_mean" in box.parameters: param_index = np.argwhere(np.array(box.parameters) == "gauss_mean")[0] samples[:, param_index] *= 1e3 # (um) -> (nm) if "gauss_sigma" in box.parameters: param_index = np.argwhere(np.array(box.parameters) == "gauss_sigma")[0] samples[:, param_index] *= 1e3 # (um) -> (nm) if box.prob_sample is not None: par_val = tuple(box.prob_sample.values()) print("Maximum posterior sample:") for key, value in box.prob_sample.items(): print(f" - {key} = {value:.2e}") for item in box.parameters: if item[0:11] == "wavelength_": param_index = box.parameters.index(item) # (um) -> (nm) box.samples[:, param_index] *= 1e3 if output is None: print("Plotting the posterior...", end="", flush=True) else: print(f"Plotting the posterior: {output}...", end="", flush=True) if "H2O" in box.parameters or "H2O_HITEMP" in box.parameters: samples = np.column_stack((samples, c_h_ratio, o_h_ratio, c_o_ratio)) if inc_luminosity: if "teff" in box.parameters and "radius" in box.parameters: teff_index = np.argwhere(np.array(box.parameters) == "teff")[0] radius_index = np.argwhere(np.array(box.parameters) == "radius")[0] lum_planet = (4.0 * np.pi * (samples[..., radius_index] * constants.R_JUP)**2 * constants.SIGMA_SB * samples[..., teff_index]**4.0 / constants.L_SUN) if "disk_teff" in box.parameters and "disk_radius" in box.parameters: teff_index = np.argwhere( np.array(box.parameters) == "disk_teff")[0] radius_index = np.argwhere( np.array(box.parameters) == "disk_radius")[0] lum_disk = (4.0 * np.pi * (samples[..., radius_index] * constants.R_JUP)**2 * constants.SIGMA_SB * samples[..., teff_index]**4.0 / constants.L_SUN) samples = np.append(samples, np.log10(lum_planet + lum_disk), axis=-1) box.parameters.append("luminosity") ndim += 1 samples = np.append(samples, lum_disk / lum_planet, axis=-1) box.parameters.append("luminosity_disk_planet") ndim += 1 else: samples = np.append(samples, np.log10(lum_planet), axis=-1) box.parameters.append("luminosity") ndim += 1 elif "teff_0" in box.parameters and "radius_0" in box.parameters: luminosity = 0.0 for i in range(100): teff_index = np.argwhere( np.array(box.parameters) == f"teff_{i}") radius_index = np.argwhere( np.array(box.parameters) == f"radius_{i}") if len(teff_index) > 0 and len(radius_index) > 0: luminosity += ( 4.0 * np.pi * (samples[..., radius_index[0]] * constants.R_JUP)**2 * constants.SIGMA_SB * samples[..., teff_index[0]]**4.0 / constants.L_SUN) else: break samples = np.append(samples, np.log10(luminosity), axis=-1) box.parameters.append("luminosity") ndim += 1 # teff_index = np.argwhere(np.array(box.parameters) == 'teff_0') # radius_index = np.argwhere(np.array(box.parameters) == 'radius_0') # # luminosity_0 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN # # samples = np.append(samples, np.log10(luminosity_0), axis=-1) # box.parameters.append('luminosity_0') # ndim += 1 # # teff_index = np.argwhere(np.array(box.parameters) == 'teff_1') # radius_index = np.argwhere(np.array(box.parameters) == 'radius_1') # # luminosity_1 = 4. * np.pi * (samples[..., radius_index[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index[0]]**4. / constants.L_SUN # # samples = np.append(samples, np.log10(luminosity_1), axis=-1) # box.parameters.append('luminosity_1') # ndim += 1 # # teff_index_0 = np.argwhere(np.array(box.parameters) == 'teff_0') # radius_index_0 = np.argwhere(np.array(box.parameters) == 'radius_0') # # teff_index_1 = np.argwhere(np.array(box.parameters) == 'teff_1') # radius_index_1 = np.argwhere(np.array(box.parameters) == 'radius_1') # # luminosity_0 = 4. * np.pi * (samples[..., radius_index_0[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index_0[0]]**4. / constants.L_SUN # # luminosity_1 = 4. * np.pi * (samples[..., radius_index_1[0]]*constants.R_JUP)**2 \ # * constants.SIGMA_SB * samples[..., teff_index_1[0]]**4. / constants.L_SUN # # samples = np.append(samples, np.log10(luminosity_0/luminosity_1), axis=-1) # box.parameters.append('luminosity_ratio') # ndim += 1 # r_tmp = samples[..., radius_index_0[0]]*constants.R_JUP # lum_diff = (luminosity_1*constants.L_SUN-luminosity_0*constants.L_SUN) # # m_mdot = (3600.*24.*365.25)*lum_diff*r_tmp/constants.GRAVITY/constants.M_JUP**2 # # samples = np.append(samples, m_mdot, axis=-1) # box.parameters.append('m_mdot') # ndim += 1 if inc_mass: if "logg" in box.parameters and "radius" in box.parameters: logg_index = np.argwhere(np.array(box.parameters) == "logg")[0] radius_index = np.argwhere(np.array(box.parameters) == "radius")[0] mass_samples = read_util.get_mass(samples[..., logg_index], samples[..., radius_index]) samples = np.append(samples, mass_samples, axis=-1) box.parameters.append("mass") ndim += 1 else: warnings.warn( "Samples with the log(g) and radius are required for 'inc_mass=True'." ) if inc_loglike: # Get ln(L) of the samples ln_prob = box.ln_prob[..., np.newaxis] # Normalized by the maximum ln(L) ln_prob -= np.amax(ln_prob) # Convert ln(L) to log10(L) log_prob = ln_prob * np.exp(1.0) # Convert log10(L) to L prob = 10.0**log_prob # Normalize to an integrated probability of 1 prob /= np.sum(prob) samples = np.append(samples, np.log10(prob), axis=-1) box.parameters.append("log_prob") ndim += 1 labels = plot_util.update_labels(box.parameters) # Check if parameter values were fixed index_sel = [] index_del = [] for i in range(ndim): if np.amin(samples[:, i]) == np.amax(samples[:, i]): index_del.append(i) else: index_sel.append(i) samples = samples[:, index_sel] for i in range(len(index_del) - 1, -1, -1): del labels[index_del[i]] ndim -= len(index_del) samples = samples.reshape((-1, ndim)) if isinstance(title_fmt, list) and len(title_fmt) != ndim: raise ValueError( f"The number of items in the list of 'title_fmt' ({len(title_fmt)}) is " f"not equal to the number of dimensions of the samples ({ndim}).") hist_titles = [] for i, item in enumerate(labels): unit_start = item.find("(") if unit_start == -1: param_label = item unit_label = None else: param_label = item[:unit_start] # Remove parenthesis from the units unit_label = item[unit_start + 1:-1] q_16, q_50, q_84 = corner.quantile(samples[:, i], [0.16, 0.5, 0.84]) q_minus, q_plus = q_50 - q_16, q_84 - q_50 if isinstance(title_fmt, str): fmt = "{{0:{0}}}".format(title_fmt).format elif isinstance(title_fmt, list): fmt = "{{0:{0}}}".format(title_fmt[i]).format best_fit = r"${{{0}}}_{{-{1}}}^{{+{2}}}$" best_fit = best_fit.format(fmt(q_50), fmt(q_minus), fmt(q_plus)) if unit_label is None: hist_title = f"{param_label} = {best_fit}" else: hist_title = f"{param_label} = {best_fit} {unit_label}" hist_titles.append(hist_title) fig = corner.corner( samples, quantiles=[0.16, 0.5, 0.84], labels=labels, label_kwargs={"fontsize": 13}, titles=hist_titles, show_titles=True, title_fmt=None, title_kwargs={"fontsize": 12}, ) axes = np.array(fig.axes).reshape((ndim, ndim)) for i in range(ndim): for j in range(ndim): if i >= j: ax = axes[i, j] ax.xaxis.set_major_formatter(ScalarFormatter(useOffset=False)) ax.yaxis.set_major_formatter(ScalarFormatter(useOffset=False)) labelleft = j == 0 and i != 0 labelbottom = i == ndim - 1 ax.tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelleft=labelleft, labelbottom=labelbottom, labelright=False, labeltop=False, ) ax.tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelleft=labelleft, labelbottom=labelbottom, labelright=False, labeltop=False, ) if limits is not None: ax.set_xlim(limits[j]) if max_prob: ax.axvline(par_val[j], color="tomato") if i > j: if max_prob: ax.axhline(par_val[i], color="tomato") ax.plot(par_val[j], par_val[i], "s", color="tomato") if limits is not None: ax.set_ylim(limits[i]) if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.26) ax.get_yaxis().set_label_coords(-0.27, 0.5) if title: fig.suptitle(title, y=1.02, fontsize=16) print(" [DONE]") if output is None: plt.show() else: plt.savefig(output, bbox_inches="tight") plt.clf() plt.close()
def run_mcmc(self, nwalkers: int, nsteps: int, guess: Union[Dict[str, float], Dict[str, None]], tag: str) -> None: """ Function to run the MCMC sampler. Parameters ---------- nwalkers : int Number of walkers. nsteps : int Number of steps per walker. guess : dict(str, float), dict(str, None) Guess of the scaling parameter. tag : str Database tag where the MCMC samples will be stored. Returns ------- NoneType None """ print('Running MCMC...') ndim = 1 initial = np.zeros((nwalkers, ndim)) for i, item in enumerate(self.modelpar): if guess[item] is not None: width = min(abs(guess[item] - self.bounds[item][0]), abs(guess[item] - self.bounds[item][1])) initial[:, i] = guess[item] + np.random.normal( 0, 0.1 * width, nwalkers) else: initial[:, i] = np.random.uniform(low=self.bounds[item][0], high=self.bounds[item][1], size=nwalkers) with Pool(processes=cpu_count()): ens_sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob, args=([ self.bounds, self.modelpar, self.objphot, self.specphot ])) ens_sampler.run_mcmc(initial, nsteps, progress=True) species_db = database.Database() species_db.add_samples(sampler='emcee', samples=ens_sampler.chain, ln_prob=ens_sampler.lnprobability, mean_accept=np.mean( ens_sampler.acceptance_fraction), spectrum=('calibration', self.spectrum), tag=tag, modelpar=self.modelpar, distance=None, spec_labels=None)
def plot_mag_posterior(tag: str, filter_name: str, burnin: int = None, xlim: Tuple[float, float] = None, output: str = 'mag_posterior.pdf') -> None: """ Function to plot the posterior distribution of the synthetic magnitudes. Parameters ---------- tag : str Database tag with the posterior samples. filter_name : str Filter name. burnin : int, None Number of burnin steps to exclude. All samples are used if set to ``None``. xlim : tuple(float, float), None Axis limits. Automatically set if set to ``None``. output : str Output filename. Returns ------- NoneType None """ mpl.rcParams['font.serif'] = ['Bitstream Vera Serif'] mpl.rcParams['font.family'] = 'serif' plt.rc('axes', edgecolor='black', linewidth=2.2) species_db = database.Database() samples = species_db.get_mcmc_photometry(tag, filter_name, burnin) print(f'Plotting photometry samples: {output}...', end='', flush=True) fig = corner.corner(samples, labels=['Magnitude'], quantiles=[0.16, 0.5, 0.84], label_kwargs={'fontsize': 13.}, show_titles=True, title_kwargs={'fontsize': 12.}, title_fmt='.2f') axes = np.array(fig.axes).reshape((1, 1)) ax = axes[0, 0] ax.tick_params(axis='both', which='major', colors='black', labelcolor='black', direction='in', width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True) ax.tick_params(axis='both', which='minor', colors='black', labelcolor='black', direction='in', width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True) if xlim is not None: ax.set_xlim(xlim) ax.get_xaxis().set_label_coords(0.5, -0.26) plt.savefig(os.getcwd() + '/' + output, bbox_inches='tight') plt.clf() plt.close() print(' [DONE]')
def plot_walkers( tag: str, nsteps: Optional[int] = None, offset: Optional[Tuple[float, float]] = None, output: Optional[str] = "walkers.pdf", ) -> None: """ Function to plot the step history of the walkers. Parameters ---------- tag : str Database tag with the samples. nsteps : int, None Number of steps that are plotted. All steps are plotted if set to ``None``. offset : tuple(float, float), None Offset of the x- and y-axis label. Default values are used if set to ``None``. output : str Output filename for the plot. The plot is shown in an interface window if the argument is set to ``None``. Returns ------- NoneType None """ if output is None: print("Plotting walkers...", end="", flush=True) else: print(f"Plotting walkers: {output}...", end="", flush=True) mpl.rcParams["font.serif"] = ["Bitstream Vera Serif"] mpl.rcParams["font.family"] = "serif" plt.rc("axes", edgecolor="black", linewidth=2.2) species_db = database.Database() box = species_db.get_samples(tag) samples = box.samples labels = plot_util.update_labels(box.parameters) if samples.ndim == 2: raise ValueError( f"The samples of '{tag}' have only 2 dimensions whereas 3 are required " f"for plotting the walkers. The plot_walkers function can only be " f"used after running the MCMC with run_mcmc and not after running " f"run_ultranest or run_multinest.") ndim = samples.shape[-1] plt.figure(1, figsize=(6, ndim * 1.5)) gridsp = mpl.gridspec.GridSpec(ndim, 1) gridsp.update(wspace=0, hspace=0.1, left=0, right=1, bottom=0, top=1) for i in range(ndim): ax = plt.subplot(gridsp[i, 0]) if i == ndim - 1: ax.tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True, ) ax.tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=True, ) else: ax.tick_params( axis="both", which="major", colors="black", labelcolor="black", direction="in", width=1, length=5, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=False, ) ax.tick_params( axis="both", which="minor", colors="black", labelcolor="black", direction="in", width=1, length=3, labelsize=12, top=True, bottom=True, left=True, right=True, labelbottom=False, ) if i == ndim - 1: ax.set_xlabel("Step number", fontsize=10) else: ax.set_xlabel("", fontsize=10) ax.set_ylabel(labels[i], fontsize=10) if offset is not None: ax.get_xaxis().set_label_coords(0.5, offset[0]) ax.get_yaxis().set_label_coords(offset[1], 0.5) else: ax.get_xaxis().set_label_coords(0.5, -0.22) ax.get_yaxis().set_label_coords(-0.09, 0.5) if nsteps is not None: ax.set_xlim(0, nsteps) for j in range(samples.shape[0]): ax.plot(samples[j, :, i], ls="-", lw=0.5, color="black", alpha=0.5) print(" [DONE]") if output is None: plt.show() else: plt.savefig(output, bbox_inches="tight") plt.clf() plt.close()
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, )