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_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, object_name: str, model: str, bounds: Dict[str, Union[Tuple[float, float], Tuple[Optional[Tuple[float, float]], Optional[Tuple[float, float]]], List[Tuple[float, float]]]], inc_phot: Union[bool, List[str]] = True, inc_spec: Union[bool, List[str]] = True, fit_corr: Optional[List[str]] = None) -> None: """ The grid of spectra is linearly interpolated for each photometric point and spectrum while taking into account the filter profile, spectral resolution, and wavelength sampling. Therefore, when fitting spectra from a model grid, the computation time of the interpolation will depend on the wavelength range, spectral resolution, and parameter space of the spectra that are stored in the database. Parameters ---------- object_name : str Object name as stored in the database with :func:`~species.data.database.Database.add_object` or :func:`~species.data.database.Database.add_companion`. model : str Atmospheric model (e.g. 'bt-settl', 'exo-rem', or 'planck'). bounds : dict(str, tuple(float, float)), None The boundaries that are used for the uniform priors. Atmospheric model parameters (e.g. ``model='bt-settl'``): - Boundaries are provided as tuple of two floats. For example, ``bounds={'teff': (1000, 1500.), 'logg': (3.5, 5.), 'radius': (0.8, 1.2)}``. - The grid boundaries are used if set to ``None``. For example, ``bounds={'teff': None, 'logg': None}``. The radius range is set to 0.8-1.5 Rjup if the boundary is set to None. Blackbody emission parameters (``model='planck'``): - Parameter boundaries have to be provided for 'teff' and 'radius'. - For a single blackbody component, the values are provided as a tuple with two floats. For example, ``bounds={'teff': (1000., 2000.), 'radius': (0.8, 1.2)}``. - For multiple blackbody component, the values are provided as a list with tuples. For example, ``bounds={'teff': [(1000., 1400.), (1200., 1600.)], 'radius': [(0.8, 1.5), (1.2, 2.)]}``. - When fitting multiple blackbody components, a prior is used which restricts the temperatures and radii to decreasing and increasing values, respectively, in the order as provided in ``bounds``. Calibration parameters: - For each spectrum/instrument, two optional parameters can be fitted to account for biases in the calibration: a scaling of the flux and a constant inflation of the uncertainties. - For example, ``bounds={'SPHERE': ((0.8, 1.2), (-18., -14.))}`` if the scaling is fitted between 0.8 and 1.2, and the error is inflated with a value between 1e-18 and 1e-14 W m-2 um-1. - The dictionary key should be equal to the database tag of the spectrum. For example, ``{'SPHERE': ((0.8, 1.2), (-18., -14.))}`` if the spectrum is stored as ``'SPHERE'`` with :func:`~species.data.database.Database.add_object`. - Each of the two calibration parameters can be set to ``None`` in which case the parameter is not used. For example, ``bounds={'SPHERE': ((0.8, 1.2), None)}``. - No calibration parameters are fitted if the spectrum name is not included in ``bounds``. ISM extinction parameters: - There are three approaches for fitting extinction. The first is with the empirical relation from Cardelli et al. (1989) for ISM extinction. - The extinction is parametrized by the V band extinction, A_V (``ism_ext``), and the reddening, R_V (``ism_red``). - The prior boundaries of ``ism_ext`` and ``ext_red`` should be provided in the ``bounds`` dictionary, for example ``bounds={'ism_ext': (0., 10.), 'ism_red': (0., 20.)}``. - Only supported by ``run_multinest``. Log-normal size distribution: - The second approach is fitting the extinction of a log-normal size distribution of grains with a crystalline MgSiO3 composition, and a homogeneous, spherical structure. - The size distribution is parameterized with a mean geometric radius (``lognorm_radius`` in um) and a geometric standard deviation (``lognorm_sigma``, dimensionless). - The extinction (``lognorm_ext``) is fitted in the V band (A_V in mag) and the wavelength-dependent extinction cross sections are interpolated from a pre-tabulated grid. - The prior boundaries of ``lognorm_radius``, ``lognorm_sigma``, and ``lognorm_ext`` should be provided in the ``bounds`` dictionary, for example ``bounds={'lognorm_radius': (0.01, 10.), 'lognorm_sigma': (1.2, 10.), 'lognorm_ext': (0., 5.)}``. - A uniform prior is used for ``lognorm_sigma`` and ``lognorm_ext``, and a log-uniform prior for ``lognorm_radius``. - Only supported by ``run_multinest``. Power-law size distribution: - The third approach is fitting the extinction of a power-law size distribution of grains, again with a crystalline MgSiO3 composition, and a homogeneous, spherical structure. - The size distribution is parameterized with a maximum radius (``powerlaw_max`` in um) and a power-law exponent (``powerlaw_exp``, dimensionless). The minimum radius is fixed to 1 nm. - The extinction (``powerlaw_ext``) is fitted in the V band (A_V in mag) and the wavelength-dependent extinction cross sections are interpolated from a pre-tabulated grid. - The prior boundaries of ``powerlaw_max``, ``powerlaw_exp``, and ``powerlaw_ext`` should be provided in the ``bounds`` dictionary, for example ``'powerlaw_max': (0.5, 10.), 'powerlaw_exp': (-5., 5.), 'powerlaw_ext': (0., 5.)}``. - A uniform prior is used for ``powerlaw_exp`` and ``powerlaw_ext``, and a log-uniform prior for ``powerlaw_max``. - Only supported by ``run_multinest``. inc_phot : bool, list(str) Include photometric data in the fit. If a boolean, either all (``True``) or none (``False``) of the data are selected. If a list, a subset of filter names (as stored in the database) can be provided. inc_spec : bool, list(str) Include spectroscopic data in the fit. If a boolean, either all (``True``) or none (``False``) of the data are selected. If a list, a subset of spectrum names (as stored in the database with :func:`~species.data.database.Database.add_object`) can be provided. fit_corr : list(str), None List with spectrum names for which the correlation length and fractional amplitude are fitted (see Wang et al. 2020). Returns ------- NoneType None """ if not inc_phot and not inc_spec: raise ValueError( 'No photometric or spectroscopic data has been selected.') if model == 'planck' and 'teff' not in bounds or 'radius' not in bounds: raise ValueError( 'The \'bounds\' dictionary should contain \'teff\' and \'radius\'.' ) self.object = read_object.ReadObject(object_name) self.distance = self.object.get_distance() if fit_corr is None: self.fit_corr = [] else: self.fit_corr = fit_corr self.model = model self.bounds = bounds if self.model == 'planck': # Fitting blackbody radiation if isinstance(bounds['teff'], list) and isinstance( bounds['radius'], list): # Update temperature and radius parameters in case of multiple blackbody components self.n_planck = len(bounds['teff']) self.modelpar = [] self.bounds = {} for i, item in enumerate(bounds['teff']): self.modelpar.append(f'teff_{i}') self.modelpar.append(f'radius_{i}') self.bounds[f'teff_{i}'] = bounds['teff'][i] self.bounds[f'radius_{i}'] = bounds['radius'][i] else: # Fitting a single blackbody compoentn self.n_planck = 1 self.modelpar = ['teff', 'radius'] self.bounds = bounds else: # Fitting self-consistent atmospheric models if self.bounds is not None: readmodel = read_model.ReadModel(self.model) bounds_grid = readmodel.get_bounds() for item in bounds_grid: if item not in self.bounds: # Set the parameter boundaries to the grid boundaries if set to None self.bounds[item] = bounds_grid[item] else: # Set all parameter boundaries to the grid boundaries readmodel = read_model.ReadModel(self.model, None, None) self.bounds = readmodel.get_bounds() if 'radius' not in self.bounds: self.bounds['radius'] = (0.8, 1.5) self.n_planck = 0 self.modelpar = readmodel.get_parameters() self.modelpar.append('radius') # Select filters and spectra if isinstance(inc_phot, bool): if inc_phot: # Select all filters if True species_db = database.Database() objectbox = species_db.get_object(object_name) inc_phot = objectbox.filters else: inc_phot = [] if isinstance(inc_spec, bool): if inc_spec: # Select all filters if True species_db = database.Database() objectbox = species_db.get_object(object_name) inc_spec = list(objectbox.spectrum.keys()) else: inc_spec = [] # Include photometric data self.objphot = [] self.modelphot = [] for item in inc_phot: if self.model == 'planck': # Create SyntheticPhotometry objects when fitting a Planck function print(f'Creating synthetic photometry: {item}...', end='', flush=True) self.modelphot.append(photometry.SyntheticPhotometry(item)) else: # Or interpolate the model grid for each filter print(f'Interpolating {item}...', end='', flush=True) readmodel = read_model.ReadModel(self.model, filter_name=item) readmodel.interpolate_grid(wavel_resample=None, smooth=False, spec_res=None) self.modelphot.append(readmodel) print(' [DONE]') # Store the flux and uncertainty for each filter obj_phot = self.object.get_photometry(item) self.objphot.append(np.array([obj_phot[2], obj_phot[3]])) # Include spectroscopic data if inc_spec: # Select all spectra self.spectrum = self.object.get_spectrum() # Select the spectrum names that are not in inc_spec spec_remove = [] for item in self.spectrum: if item not in inc_spec: spec_remove.append(item) # Remove the spectra that are not included in inc_spec for item in spec_remove: del self.spectrum[item] self.n_corr_par = 0 for item in self.spectrum: if item in self.fit_corr: self.modelpar.append(f'corr_len_{item}') self.modelpar.append(f'corr_amp_{item}') self.bounds[f'corr_len_{item}'] = ( -3., 0.) # log10(corr_len) (um) self.bounds[f'corr_amp_{item}'] = (0., 1.) self.n_corr_par += 2 self.modelspec = [] if self.model != 'planck': for key, value in self.spectrum.items(): print(f'\rInterpolating {key}...', end='', flush=True) wavel_range = (0.9 * value[0][0, 0], 1.1 * value[0][-1, 0]) readmodel = read_model.ReadModel(self.model, wavel_range=wavel_range) readmodel.interpolate_grid( wavel_resample=self.spectrum[key][0][:, 0], smooth=True, spec_res=self.spectrum[key][3]) self.modelspec.append(readmodel) print(' [DONE]') else: self.spectrum = {} self.modelspec = None self.n_corr_par = 0 for item in self.spectrum: if item in bounds: if bounds[item][0] is not None: # Add the flux scaling parameter self.modelpar.append(f'scaling_{item}') self.bounds[f'scaling_{item}'] = (bounds[item][0][0], bounds[item][0][1]) if bounds[item][1] is not None: # Add the error offset parameters self.modelpar.append(f'error_{item}') self.bounds[f'error_{item}'] = (bounds[item][1][0], bounds[item][1][1]) if item in self.bounds: del self.bounds[item] if 'lognorm_radius' in self.bounds and 'lognorm_sigma' in self.bounds and \ 'lognorm_ext' in self.bounds: self.cross_sections, _, _ = dust_util.interp_lognorm( inc_phot, inc_spec, self.spectrum) self.modelpar.append('lognorm_radius') self.modelpar.append('lognorm_sigma') self.modelpar.append('lognorm_ext') self.bounds['lognorm_radius'] = ( np.log10(self.bounds['lognorm_radius'][0]), np.log10(self.bounds['lognorm_radius'][1])) elif 'powerlaw_max' in self.bounds and 'powerlaw_exp' in self.bounds and \ 'powerlaw_ext' in self.bounds: self.cross_sections, _, _ = dust_util.interp_powerlaw( inc_phot, inc_spec, self.spectrum) self.modelpar.append('powerlaw_max') self.modelpar.append('powerlaw_exp') self.modelpar.append('powerlaw_ext') self.bounds['powerlaw_max'] = (np.log10( self.bounds['powerlaw_max'][0]), np.log10( self.bounds['powerlaw_max'][1])) else: self.cross_sections = None if 'ism_ext' in self.bounds and 'ism_red' in self.bounds: self.modelpar.append('ism_ext') self.modelpar.append('ism_red') print(f'Fitting {len(self.modelpar)} parameters:') for item in self.modelpar: print(f' - {item}') print('Prior boundaries:') for key, value in self.bounds.items(): print(f' - {key} = {value}')