def evaluate(x, tau, x_0, fwhm): if tau == 0.0: return np.full((len(x)), 0.0) else: profile = Drude1D(amplitude=1.0, fwhm=fwhm, x_0=x_0) tau_x = tau * profile(x) return (1.0 - np.exp(-1.0 * tau_x)) / tau_x
def kvt(in_x): """ Output the kvt extinction curve """ # fmt: off kvt_wav = np.array([ 8.0, 8.2, 8.4, 8.6, 8.8, 9.0, 9.2, 9.4, 9.6, 9.7, 9.75, 9.8, 10.0, 10.2, 10.4, 10.6, 10.8, 11.0, 11.2, 11.4, 11.6, 11.8, 12.0, 12.2, 12.4, 12.6, 12.7 ]) kvt_int = np.array([ .06, .09, .16, .275, .415, .575, .755, .895, .98, .99, 1.0, .99, .94, .83, .745, .655, .58, .525, .43, .35, .27, .20, .13, .09, .06, .045, .04314 ]) # fmt: on # Extend kvt profile to shorter wavelengths if min(in_x) < min(kvt_wav): kvt_wav_short = in_x[in_x < min(kvt_wav)] kvt_int_short_tmp = min(kvt_int) * np.exp( 2.03 * (kvt_wav_short - min(kvt_wav))) # Since kvt_int_shoft_tmp does not reach min(kvt_int), # we scale it to stitch it. kvt_int_short = kvt_int_short_tmp * (kvt_int[0] / max(kvt_int_short_tmp)) spline_x = np.concatenate([kvt_wav_short, kvt_wav]) spline_y = np.concatenate([kvt_int_short, kvt_int]) else: spline_x = kvt_wav spline_y = kvt_int intfunc = interpolate.interp1d(spline_x, spline_y) in_x_spline = in_x[in_x < max(kvt_wav)] new_spline_y = intfunc(in_x_spline) nf = Drude1D(amplitude=0.4, x_0=18.0, fwhm=0.247 * 18.0) in_x_drude = in_x[in_x >= max(kvt_wav)] ext = np.concatenate([new_spline_y, nf(in_x_drude)]) # Extend to ~2 um # assuming beta is 0.1 beta = 0.1 y = (1.0 - beta) * ext + beta * (9.7 / in_x)**1.7 return y
def __init__( self, obs_x, obs_y, estimate_start=False, param_info=None, filename=None, tformat=None, ): """ Setup a variant based on inputs. Generates an astropy.modeling compound model. """ # check that param_info or filename is set if filename is None and param_info is None: raise ValueError("Either param_info or filename need to be set \ when initializing a PAHFITBase object") # read in the parameter info from a file if filename is not None: param_info = self.read(filename, tformat=tformat) if estimate_start: # guess values and update starting point (if not set fixed) based on the input spectrum param_info = self.estimate_init(obs_x, obs_y, param_info) if not param_info: raise ValueError("No parameter information set.") self.param_info = param_info bb_info = param_info[0] dust_features = param_info[1] h2_features = param_info[2] ion_features = param_info[3] att_info = param_info[4] # setup the model self.model = None self.bb_info = bb_info if bb_info is not None: bbs = [] for k in range(len(bb_info["names"])): BBClass = ModifiedBlackBody1D if bb_info["modified"][ k] else BlackBody1D bbs.append( BBClass( name=bb_info["names"][k], temperature=bb_info["temps"][k], amplitude=bb_info["amps"][k], bounds={ "temperature": bb_info["temps_limits"][k], "amplitude": bb_info["amps_limits"][k], }, fixed={ "temperature": bb_info["temps_fixed"][k], "amplitude": bb_info["amps_fixed"][k], }, )) self.model = sum(bbs[1:], bbs[0]) self.dust_features = dust_features if dust_features is not None: df = [] for k in range(len(dust_features["names"])): df.append( Drude1D( name=dust_features["names"][k], amplitude=dust_features["amps"][k], x_0=dust_features["x_0"][k], fwhm=dust_features["fwhms"][k], bounds={ "amplitude": dust_features["amps_limits"][k], "x_0": dust_features["x_0_limits"][k], "fwhm": dust_features["fwhms_limits"][k], }, fixed={ "amplitude": dust_features["amps_fixed"][k], "x_0": dust_features["x_0_fixed"][k], "fwhm": dust_features["fwhms_fixed"][k], }, )) df = sum(df[1:], df[0]) if self.model: self.model += df else: self.model = df self.h2_features = h2_features if h2_features is not None: h2 = [] for k in range(len(h2_features["names"])): h2.append( Gaussian1D( name=h2_features["names"][k], amplitude=h2_features["amps"][k], mean=h2_features["x_0"][k], stddev=h2_features["fwhms"][k] / 2.355, bounds={ "amplitude": h2_features["amps_limits"][k], "mean": h2_features["x_0_limits"][k], "stddev": ( h2_features["fwhms"][k] * 0.9 / 2.355, h2_features["fwhms"][k] * 1.1 / 2.355, ), }, fixed={ "amplitude": h2_features["amps_fixed"][k], "mean": h2_features["x_0_fixed"][k], "stddev": h2_features["fwhms_fixed"][k], }, )) h2 = sum(h2[1:], h2[0]) if self.model: self.model += h2 else: self.model = h2 self.ion_features = ion_features if ion_features is not None: ions = [] for k in range(len(ion_features["names"])): ions.append( Gaussian1D( name=ion_features["names"][k], amplitude=ion_features["amps"][k], mean=ion_features["x_0"][k], stddev=ion_features["fwhms"][k] / 2.355, bounds={ "amplitude": ion_features["amps_limits"][k], "mean": ion_features["x_0_limits"][k], "stddev": ( ion_features["fwhms"][k] * 0.9 / 2.355, ion_features["fwhms"][k] * 1.1 / 2.355, ), }, fixed={ "amplitude": ion_features["amps_fixed"][k], "mean": ion_features["x_0_fixed"][k], "stddev": ion_features["fwhms_fixed"][k], }, )) ions = sum(ions[1:], ions[0]) if self.model: self.model += ions else: self.model = ions # add additional att components to the model if necessary if not self.model: raise ValueError("No model components found") self.att_info = att_info if att_info is not None: for k in range(len(att_info["names"])): if ( att_info["names"][k] == "S07_att" ): # Only loop through att components that can be parameterized self.model *= S07_attenuation( name=att_info["names"][k], tau_sil=att_info["amps"][k], bounds={"tau_sil": att_info["amps_limits"][k]}, fixed={"tau_sil": att_info["amps_fixed"][k]}, ) else: self.model *= att_Drude1D( name=att_info["names"][k], tau=att_info["amps"][k], x_0=att_info["x_0"][k], fwhm=att_info["fwhms"][k], bounds={ "tau": att_info["amps_limits"][k], "fwhm": att_info["fwhms_limits"][k], }, fixed={"x_0": att_info["x_0_fixed"][k]}, )
def __init__(self, param_info=None, filename=None, tformat=None): """ Setup a variant based on inputs. Generates an astropy.modeling compound model. """ # check that param_info or filename is set if filename is None and param_info is None: raise ValueError( "Either param_info or filename need to be set \ when initializing a PAHFITBase object" ) # read in the parameter info from a file if filename is not None: param_info = self.read(filename, tformat=tformat) bb_info = param_info[0] dust_features = param_info[1] h2_features = param_info[2] ion_features = param_info[3] att_info = param_info[4] # setup the model self.bb_info = bb_info if bb_info is not None: # 1st component defines the overall model variable self.model = BlackBody1D( name=bb_info["names"][0], temperature=bb_info["temps"][0], amplitude=bb_info["amps"][0], bounds={ "temperature": bb_info["temps_limits"][0], "amplitude": bb_info["amps_limits"][0], }, fixed={ "temperature": bb_info["temps_fixed"][0], "amplitude": bb_info["amps_fixed"][0], }, ) for k in range(1, len(bb_info["names"])): self.model += BlackBody1D( name=bb_info["names"][k], temperature=bb_info["temps"][k], amplitude=bb_info["amps"][k], bounds={ "temperature": bb_info["temps_limits"][k], "amplitude": bb_info["amps_limits"][k], }, fixed={ "temperature": bb_info["temps_fixed"][k], "amplitude": bb_info["amps_fixed"][k], }, ) self.dust_features = dust_features if dust_features is not None: for k in range(len(dust_features["names"])): self.model += Drude1D( name=dust_features["names"][k], amplitude=dust_features["amps"][k], x_0=dust_features["x_0"][k], fwhm=dust_features["fwhms"][k], bounds={ "amplitude": dust_features["amps_limits"][k], "x_0": dust_features["x_0_limits"][k], "fwhm": dust_features["fwhms_limits"][k], }, fixed={ "amplitude": dust_features["amps_fixed"][k], "x_0": dust_features["x_0_fixed"][k], "fwhm": dust_features["fwhms_fixed"][k], }, ) self.h2_features = h2_features if h2_features is not None: for k in range(len(h2_features["names"])): self.model += Gaussian1D( name=h2_features["names"][k], amplitude=h2_features["amps"][k], mean=h2_features["x_0"][k], stddev=h2_features["fwhms"][k] / 2.355, bounds={ "amplitude": h2_features["amps_limits"][k], "mean": h2_features["x_0_limits"][k], "stddev": ( h2_features["fwhms_limits"][k][0] / 2.355, h2_features["fwhms_limits"][k][1] / 2.355, ), }, fixed={ "amplitude": h2_features["amps_fixed"][k], "mean": h2_features["x_0_fixed"][k], "stddev": h2_features["fwhms_fixed"][k], }, ) self.ion_features = ion_features if ion_features is not None: for k in range(len(ion_features["names"])): self.model += Gaussian1D( name=ion_features["names"][k], amplitude=ion_features["amps"][k], mean=ion_features["x_0"][k], stddev=ion_features["fwhms"][k] / 2.355, bounds={ "amplitude": ion_features["amps_limits"][k], "mean": ion_features["x_0_limits"][k], "stddev": ( ion_features["fwhms_limits"][k][0] / 2.355, ion_features["fwhms_limits"][k][1] / 2.355, ), }, fixed={ "amplitude": ion_features["amps_fixed"][k], "mean": ion_features["x_0_fixed"][k], "stddev": ion_features["fwhms_fixed"][k], }, ) # apply the attenuation to *all* the components self.model *= S07_attenuation( name=att_info["names"][0], tau_sil=att_info["amps"][0], bounds={"tau_sil": att_info["amps_limits"][0]}, fixed={"tau_sil": att_info["amps_fixed"][0]}, )
def eqws(comp_type, x_0, amp, fwhm_stddev, obs_fit): """ Calculate the emission features equivalent width (integral[(I_nu-I_cont)/I_cont d_lam]) in microns. Parameters ---------- comp_type : string type of emission component (Drude1D/Gaussian) x_0 : float central wavelength of the feature. amp : float central intensity of the feature. fwhm_stddev : float fwhm or stddev of the feature depending on comp_type. Returns ------- eqw : float the equivalent width of the feature """ # Check if the emission component is Gaussian and calculate fwhm. if comp_type == 'Gaussian1D': fwhm = 2 * fwhm_stddev * np.sqrt(2 * np.log(2)) else: fwhm = fwhm_stddev # Get range and wavelength region for integration. low = x_0 - (fwhm * 6) lmin = low if low > 0 else 0. lmax = x_0 + (fwhm * 6) # lam = np.arange(100) / 99 * (lmax - lmin) + lmin lam = np.linspace(lmin, lmax, num=100) # Calculate the continuum and feature components in the integration range. cont_components = [] for cmodel in obs_fit: if isinstance(cmodel, BlackBody1D): cont_components.append(cmodel) cont_model = cont_components[0] for cmodel in cont_components[1:]: cont_model += cmodel continuum = np.nan_to_num(cont_model(lam)) if comp_type == 'Drude1D': drude = Drude1D(amplitude=amp, x_0=x_0, fwhm=fwhm) lnu = drude(lam) elif comp_type == 'Gaussian1D': gauss = Gaussian1D(amplitude=amp, mean=x_0, stddev=fwhm_stddev) lnu = gauss(lam) # Following the EQW calculation in IDL PAHFIT, we define a default broad limit (bl). # Set bl to replace the continuum in the EQW calculation # by its profile-averaged value for fractional FWHM greater than that limit. # Useful when the EQW requires extrapolation to regions where the continuum # can vanish, such that f_line/f_continuum diverges. # Default value bl = 0.05. bl = 0.05 # Calculate EQW. if fwhm / x_0 > bl: ilam = integrate.simpson(lnu, lam) weighted_cont = integrate.simpson(lnu * continuum, lam) / ilam eqw = ilam / weighted_cont else: eqw = integrate.simpson(lnu / continuum, lam) return eqw