def mergeOpacity(species,lowres='nom_res',highres='high_res'): ''' Merge high-res opacities into a grid of low-res opacities. The wavelength range of the inserted high res opacities is taken from the given high res grid. @param species: The dust species for which this is done. This is also the name of the folder in ~/MCMax/DustOpacities/ that contains the data files. @type species: string @keyword lowres: The subfolder in ~/MCMax/DustOpacities/species containing the low resolution datafiles. (default: low_res) @type lowres: string @keyword highres: The subfolder in ~/MCMax/DustOpacities/species containing the high resolution datafiles. (default: high_res) @type highres: string ''' path = os.path.join(cc.path.mopac,species) lowres_files = [f for f in glob(os.path.join(path,lowres,'*')) if f[-5:] == '.opac'] highres_files = [f for f in glob(os.path.join(path,highres,'*')) if f[-5:] == '.opac'] files = set([os.path.split(f)[1] for f in lowres_files] + \ [os.path.split(f)[1] for f in highres_files]) for f in files: hdfile = os.path.join(path,highres,f) ldfile = os.path.join(path,lowres,f) if os.path.isfile(ldfile) and os.path.isfile(hdfile): hd = DataIO.readCols(hdfile) ld = DataIO.readCols(ldfile) hdw = hd[0] ldw = ld[0] wmin = hdw[0] wmax = hdw[-1] ld_low = [list(col[ldw<wmin]) for col in ld] ld_high = [list(col[ldw>wmax]) for col in ld] hd = [list(col) for col in hd] merged = [ld_low[i] + hd[i] + ld_high[i] for i in range(len(hd))] DataIO.writeCols(filename=os.path.join(path,f),cols=merged)
def readModelSpectrum(dpath, rt_spec=1, fn_spec='spectrum45.0.dat'): ''' Read the model output spectrum. If no ray-tracing is requested or no ray-tracing output is found, the average of the MC spectra is taken. @param dpath: folder that contains the MCMax outputfiles @type dpath: string @keyword rt_spec: If a ray-traced spectrum is requested (default: 1) @type rt_spec: bool @keyword fn_spec: The filename of the ray-traced spectrum. Typically this is the default name, but can be different depending on the ray-tracing angle (inclination) that is used. Not used if MCSpec are used. (default: spectrum45.0.dat) @type fn_spec: str @return: The wavelength and flux grids (micron,Jy) @rtype: (array,array) ''' rt_spec = int(rt_spec) try: if rt_spec: dfile = os.path.join(dpath, fn_spec) this_data = DataIO.readCols(dfile) #- if the lists are not empty if list(this_data[0]) and list(this_data[1]): w = this_data[0] f = this_data[1] else: raise IOError else: raise IOError except IOError: print 'No spectrum was found or ray-tracing is off for ' + \ 'this model. Taking average of theta-grid MCSpectra.' dfiles = glob(os.path.join(dpath, 'MCSpec*.dat')) w = DataIO.readCols(filename=dfiles[0])[0] mcy_list = [DataIO.readCols(f)[1] for f in dfiles] f = sum(mcy_list) / len(mcy_list) return (w, f)
def plotExtinction(self, star_grid=[], models=[], plot_default=1, cfg=''): """ Plotting wavelength dependent extinction efficiencies wrt grain size. This always depends on a star_grid or one created from a list of MCMax model ids. Plotted are the total efficiencies, including relative weights between the included dust species. This is the input for GASTRoNOoM! @keyword star_grid: List of Star() instances. If default, model ids have to be given. (default: []) @type star_grid: list[Star()] @keyword models: The model ids, only required if star_grid is [] (default: []) @type models: list[string] @keyword cfg: path to the Plotting2.plotCols config file. If default, the hard-coded default plotting options are used. (default: '') @type cfg: string """ print '***********************************' print '** Plotting Q_ext/a.' if not star_grid and not models: print 'Input is undefined. Aborting.' return elif not star_grid and models: star_grid = self.makeMCMaxStars(models=models) x = [] y = [] keys = [] for star in star_grid: try: inputfile = os.path.join(cc.path.gdata, star['TEMDUST_FILENAME']) opacities = DataIO.readCols(filename=inputfile) x.append(opacities[0]) y.append(opacities[1]) keys.append('$Q_\mathrm{ext}/a$ for MCMax %s'\ %star['LAST_MCMAX_MODEL'].replace('_','\_')) except IOError: pass filename = os.path.join(self.pplot,'gastronoom_opacities_%s'\ %star['LAST_MCMAX_MODEL']) title = 'GASTRoNOoM Extinction Efficiencies in %s'\ %(self.star_name_plots) filename = Plotting2.plotCols(x=x,y=y,cfg=cfg,filename=filename,\ xaxis='$\lambda$ ($\mu$m)',keytags=keys,\ yaxis='$Q_{ext}/a$ (cm$^{-1}$)',\ plot_title=title,key_location=(0.7,0.6),\ xlogscale=1,ylogscale=1,fontsize_key=20) print '** The extinction efficiency plot can be found at:' print filename print '***********************************'
def getSphinxConvolution(self,star,fn): ''' Read the sphinx convolution and return if it has already been done. Returns None if the convolution is not available. @param star: The Star() object @type star: Star() @param fn: The filename of the dataset (band) for which the convolution is to be returned. @type fn: str @return: The sphinx convolution result. (wavelength, flux) @rtype: array ''' this_id = star['LAST_PACS_MODEL'] if not this_id: return ([],[]) fn = os.path.split(fn)[1] sphinx_file = os.path.join(cc.path.gout,'stars',self.star_name,\ 'PACS_results',this_id,'%s_%s'%('sphinx',fn)) return DataIO.readCols(sphinx_file)
def readData(self): ''' Read in data, taking special care of NaNs. Four colums are taken as input! wave - contsub - original - continuum Two columns still works, but may result in errors in other places in the code. Data are always read in Jy versus micron, for both SPIRE and PACS. ''' self.data_wave_list = [] self.data_flux_list = [] self.data_original_list = [] self.data_continuum_list = [] for filename in self.data_filenames: data = DataIO.readCols(filename=filename, nans=1) self.data_wave_list.append(data[0]) self.data_flux_list.append(data[1]) if len(data) == 2: continue self.data_original_list.append(data[2]) self.data_continuum_list.append(data[3])
def readDustInfo(self): """ Read all column densities, min/max temperatures and min/max radii for the species involved in the MCMax model. Note that the self.coldens dictionary does not give real column densities! This dict merely gives column densities in a prescribed shell with given min and max radius, in order to compare with the H2 col density. """ dens = self.star.getDustDensity() temp = self.star.getDustTemperature() compf = os.path.join( cc.path.mcmax, self.star.path_mcmax, "models", self.star["LAST_MCMAX_MODEL"], "composition.dat" ) comp = DataIO.readCols(compf) self.rad = comp.pop(0) * self.au self.r_outer = self.rad[-1] for species in self.star.getDustList(): # - Save the actual density profile for this dust species, as well # - as calculating the full column density of a dust species. self.dustfractions[species] = comp.pop(0) self.compd[species] = self.dustfractions[species] * dens self.fullcoldens[species] = trapz(x=self.rad, y=self.compd[species]) # - Determine the column density from 90% of the dust species formed # - onward, based on the mass fractions! # - Not before, because the comparison with H2 must be made, # - and this will skew the result if not solely looking at where the # - dust has (almost) all been formed. # - We also save min amd max radii, for use with the H2 calculation a_species = self.star["A_%s" % species] maxdens = max(self.compd[species]) mindens = maxdens * 10 ** (-10) radsel = self.rad[(self.dustfractions[species] > 0.9 * a_species) * (self.compd[species] > mindens)] denssel = self.compd[species][ (self.dustfractions[species] > 0.9 * a_species) * (self.compd[species] > mindens) ] self.coldens[species] = trapz(x=radsel, y=denssel) if radsel.size: self.r_min_cd[species] = radsel[0] self.r_max_cd[species] = radsel[-1] else: print "Threshold dust mass fraction not reached for %s." % species self.r_min_cd[species] = 0 self.r_max_cd[species] = 0 # - Determine the actual destruction radius and temperature. # - Taken where the density reaches 1% of the maximum density # - (not mass fraction). self.r_des[species] = self.rad[self.compd[species] > (maxdens * 0.01)][0] self.t_des[species] = temp[self.compd[species] > (maxdens * 0.01)][0] # - e-10 as limit for minimum is ok, because if shell is 100000 R* # - the mass conservation dictates ~ (10^5)^2 = 10^10 (r^2 law) # - decrease in density. Shells this big dont occur anyway. self.r_max[species] = self.rad[self.compd[species] > mindens][-1] self.t_min[species] = temp[self.compd[species] > mindens][-1]
def updateDustMCMaxDatabase(filename): ''' Update dust filenames in MCMax database with the new OPAC_PATH system. @param filename: The file and path to the MCMax database. @type filename: str ''' i = 0 new_filename = '%s_new'%(filename) db_old = Database(filename) db_new = Database(new_filename) path = os.path.join(cc.path.usr,'Dust_updatefile.dat') dustfiles = DataIO.readCols(path) pfn_old = list(dustfiles[0]) pfn_new = list(dustfiles[1]) for k,v in db_old.items(): dd = v['dust_species'] dd_new = dict() for pfn,cont in dd.items(): try: new_key = pfn_new[pfn_old.index(pfn)] dd_new[new_key] = cont except ValueError: dd_new[pfn] = cont v['dust_species'] = dd_new db_new[k] = v db_new.sync()
def plotExtinction(self,star_grid=[],models=[],plot_default=1,cfg=''): """ Plotting wavelength dependent extinction efficiencies wrt grain size. This always depends on a star_grid or one created from a list of MCMax model ids. Plotted are the total efficiencies, including relative weights between the included dust species. This is the input for GASTRoNOoM! @keyword star_grid: List of Star() instances. If default, model ids have to be given. (default: []) @type star_grid: list[Star()] @keyword models: The model ids, only required if star_grid is [] (default: []) @type models: list[string] @keyword cfg: path to the Plotting2.plotCols config file. If default, the hard-coded default plotting options are used. (default: '') @type cfg: string """ print '***********************************' print '** Plotting Q_ext/a.' if not star_grid and not models: print 'Input is undefined. Aborting.' return elif not star_grid and models: star_grid = self.makeMCMaxStars(models=models) x = [] y = [] keys = [] for star in star_grid: try: inputfile = os.path.join(cc.path.gdata,star['TEMDUST_FILENAME']) opacities = DataIO.readCols(filename=inputfile) x.append(opacities[0]) y.append(opacities[1]) keys.append('$Q_\mathrm{ext}/a$ for MCMax %s'\ %star['LAST_MCMAX_MODEL'].replace('_','\_')) except IOError: pass filename = os.path.join(self.pplot,'gastronoom_opacities_%s'\ %star['LAST_MCMAX_MODEL']) title = 'GASTRoNOoM Extinction Efficiencies in %s'\ %(self.star_name_plots) filename = Plotting2.plotCols(x=x,y=y,cfg=cfg,filename=filename,\ xaxis='$\lambda$ ($\mu$m)',keytags=keys,\ yaxis='$Q_{ext}/a$ (cm$^{-1}$)',\ plot_title=title,key_location=(0.7,0.6),\ xlogscale=1,ylogscale=1,fontsize_key=20) print '** The extinction efficiency plot can be found at:' print filename print '***********************************'
def updateDustMCMaxDatabase(filename): """ Update dust filenames in MCMax database with the new OPAC_PATH system. @param filename: The file and path to the MCMax database. @type filename: str """ i = 0 new_filename = "%s_new" % (filename) db_old = Database(filename) db_new = Database(new_filename) path = os.path.join(cc.path.usr, "Dust_updatefile.dat") dustfiles = DataIO.readCols(path) pfn_old = list(dustfiles[0]) pfn_new = list(dustfiles[1]) for k, v in db_old.items(): dd = v["dust_species"] dd_new = dict() for pfn, cont in dd.items(): try: new_key = pfn_new[pfn_old.index(pfn)] dd_new[new_key] = cont except ValueError: dd_new[pfn] = cont v["dust_species"] = dd_new db_new[k] = v db_new.sync()
def readData(self): ''' Read the raw SED data. ''' for dt,fn in zip(self.data_types,self.data_filenames): data = DataIO.readCols(fn,nans=0) #-- Currently, error bars only available for these types of data. if 'Photometric' in dt or 'MIDI' in dt or 'Sacha' in dt: #-- Sort MIDI data if 'MIDI' in dt: cdat = [dd[(data[0]<=13.)*(data[0]>=8.)] for dd in data] i = argsort(cdat[0]) self.data[(dt,fn)] = (cdat[0][i],cdat[1][i],cdat[2][i]) else: self.data[(dt,fn)] = (data[0],data[1],data[2]) if dt == 'Photometric_IvS': self.photbands = data[3] self.photwave = data[0] else: #-- Still sorting for PACS. Obsolete when separate bands for # PACS are available. i = argsort(data[0]) self.data[(dt,fn)] = (data[0][i],data[1][i])
def readData(self): ''' Read the raw SED data. ''' for dt, fn in zip(self.data_types, self.data_filenames): data = DataIO.readCols(fn, nans=0) #-- Currently, error bars only available for these types of data. if 'Photometric' in dt or 'MIDI' in dt or 'Sacha' in dt: #-- Sort MIDI data if 'MIDI' in dt: cdat = [ dd[(data[0] <= 13.) * (data[0] >= 8.)] for dd in data ] i = argsort(cdat[0]) self.data[(dt, fn)] = (cdat[0][i], cdat[1][i], cdat[2][i]) else: self.data[(dt, fn)] = (data[0], data[1], data[2]) if dt == 'Photometric_IvS': self.photbands = data[3] self.photwave = data[0] else: #-- Still sorting for PACS. Obsolete when separate bands for # PACS are available. i = argsort(data[0]) self.data[(dt, fn)] = (data[0][i], data[1][i])
def readData(self): ''' Read in data, taking special care of NaNs. Four colums are taken as input! wave - contsub - original - continuum Two columns still works, but may result in errors in other places in the code. Data are always read in Jy versus micron, for both SPIRE and PACS. ''' self.data_wave_list = [] self.data_flux_list = [] self.data_original_list = [] self.data_continuum_list = [] for filename in self.data_filenames: data = DataIO.readCols(filename=filename,nans=1) self.data_wave_list.append(data[0]) self.data_flux_list.append(data[1]) if len(data) == 2: continue self.data_original_list.append(data[2]) self.data_continuum_list.append(data[3])
def readModelSpectrum(dpath,rt_sed=1,fn_spec='spectrum45.0.dat'): ''' Read the model output spectrum. If no ray-tracing is requested or no ray-tracing output is found, the average of the MC spectra is taken. @param dpath: folder that contains the MCMax outputfiles @type dpath: string @keyword rt_sed: If a ray-traced spectrum is requested (default: 1) @type rt_sed: bool @keyword fn_spec: The filename of the ray-traced spectrum. Typically this is the default name, but can be different depending on the ray-tracing angle that is used. Not used if MCSpec are used. (default: spectrum45.0.dat) @type fn_spec: str @return: The wavelength and flux grids (micron,Jy) @rtype: (array,array) ''' rt_sed = int(rt_sed) try: if rt_sed: dfile = os.path.join(dpath,fn_spec) this_data = DataIO.readCols(dfile) #- if the lists are not empty if list(this_data[0]) and list(this_data[1]): w = this_data[0] f = this_data[1] else: raise IOError else: raise IOError except IOError: print 'No spectrum was found or ray-tracing is off for ' + \ 'this model. Taking average of theta-grid MCSpectra.' dfiles = glob(os.path.join(dpath,'MCSpec*.dat')) w = DataIO.readCols(filename=dfiles[0])[0] mcy_list = [DataIO.readCols(f)[1] for f in dfiles] f = sum(mcy_list)/len(mcy_list) return (w,f)
def parseImpact(self): ''' Parse sphinx file 1, which includes all the impact parameter info. The output is stored in dict self.sph1. ''' self.sph1 = dict() self.contents['sph1'] = self.sph1 data = DataIO.readCols(self.filename.replace('*', '1'), start_row=1) self.sph1['impact'] = data[0] self.sph1['norm_intens'] = data[1] self.sph1['weighted_intens'] = data[2] self.sph1['sum_intens_p'] = data[3] self.sph1['sum_intens'] = data[4]
def coolingDbRetrieval(path_gastronoom, r_outer=None): """ Reconstruct a cooling database based on the mline database and the GASTRoNOoM inputfiles. Only works if the water MOLECULE convenience keywords, the MOLECULE R_OUTER and/or the MOLECULE ENHANCE_ABUNDANCE_FACTOR keywords were not adapted! @param path_gastronoom: The path_gastronoom to the output folder @type path_gastronoom: string @keyword r_outer: The outer radius used for the cooling model, regardless of the outer_r_mode parameter. (default: None) @type r_outer: float """ # -- Convenience path cc.path.gout = os.path.join(cc.path.gastronoom, path_gastronoom) coolkeys_path = os.path.join(cc.path.aux, "Input_Keywords_Cooling.dat") coolkeys = DataIO.readCols(coolkeys_path, make_float=0, make_array=0)[0] extra_keys = [ "ENHANCE_ABUNDANCE_FACTOR", "MOLECULE_TABLE", "ISOTOPE_TABLE", "ABUNDANCE_FILENAME", "NUMBER_INPUT_ABUNDANCE_VALUES", "KEYWORD_TABLE", ] coolkeys = [k for k in coolkeys if k not in extra_keys] cool_db_path = os.path.join(cc.path.gout, "GASTRoNOoM_cooling_models.db") ml_db_path = os.path.join(cc.path.gout, "GASTRoNOoM_mline_models.db") subprocess.call(["mv %s %s_backupCoolDbRetrieval" % (cool_db_path, cool_db_path)], shell=True) cool_db = Database(db_path=cool_db_path) ml_db = Database(db_path=ml_db_path) for ml_id in ml_db.keys(): file_path = os.path.join(cc.path.gout, "models", "gastronoom_%s.inp" % ml_id) input_dict = DataIO.readDict(file_path) input_dict = dict([(k, v) for k, v in input_dict.items() if k in coolkeys]) cool_db[ml_id] = input_dict if r_outer <> None: cool_db[ml_id]["R_OUTER"] = r_outer cool_db.sync()
def parseImpact(self): """ Parse sphinx file 1, which includes all the impact parameter info. The output is stored in dict self.sph1. """ self.sph1 = dict() self.contents["sph1"] = self.sph1 data = DataIO.readCols(self.filename.replace("*", "1"), start_row=1) self.sph1["impact"] = data[0] self.sph1["norm_intens"] = data[1] self.sph1["weighted_intens"] = data[2] self.sph1["sum_intens_p"] = data[3] self.sph1["sum_intens"] = data[4]
def coolingDbRetrieval(path_gastronoom,r_outer=None): ''' Reconstruct a cooling database based on the mline database and the GASTRoNOoM inputfiles. Only works if the water MOLECULE convenience keywords, the MOLECULE R_OUTER and/or the MOLECULE ENHANCE_ABUNDANCE_FACTOR keywords were not adapted! @param path_gastronoom: The path_gastronoom to the output folder @type path_gastronoom: string @keyword r_outer: The outer radius used for the cooling model, regardless of the outer_r_mode parameter. (default: None) @type r_outer: float ''' #-- Convenience path cc.path.gout = os.path.join(cc.path.gastronoom,path_gastronoom) coolkeys_path = os.path.join(cc.path.aux,'Input_Keywords_Cooling.dat') coolkeys = DataIO.readCols(coolkeys_path,make_float=0,make_array=0)[0] extra_keys = ['ENHANCE_ABUNDANCE_FACTOR','MOLECULE_TABLE','ISOTOPE_TABLE',\ 'ABUNDANCE_FILENAME','NUMBER_INPUT_ABUNDANCE_VALUES',\ 'KEYWORD_TABLE'] coolkeys = [k for k in coolkeys if k not in extra_keys] cool_db_path = os.path.join(cc.path.gout,'GASTRoNOoM_cooling_models.db') ml_db_path = os.path.join(cc.path.gout,'GASTRoNOoM_mline_models.db') subprocess.call(['mv %s %s_backupCoolDbRetrieval'\ %(cool_db_path,cool_db_path)],shell=True) cool_db = Database(db_path=cool_db_path) ml_db = Database(db_path=ml_db_path) for ml_id in ml_db.keys(): file_path = os.path.join(cc.path.gout,'models',\ 'gastronoom_%s.inp'%ml_id) input_dict = DataIO.readDict(file_path) input_dict = dict([(k,v) for k,v in input_dict.items() if k in coolkeys]) cool_db[ml_id] = input_dict if r_outer <> None: cool_db[ml_id]['R_OUTER'] = r_outer cool_db.sync()
def readVisibilities(dpath,fn_vis='visibility01.0.dat'): ''' Read the model output visibilities, either as function of wavelength or baseline. @param dpath: folder that contains the MCMax outputfiles @type dpath: string @keyword fn_spec: The filename of the ray-traced visibilities. Typically this is the default name, but can be different depending on the inclination (or baseline) that is used. (default: visibility01.0.dat) @type fn_spec: str @return: A dictionary containing either wavelength or baseline, the flux, and the visibilities for either given baselines or wavelengths @rtype: dict ''' #-- Read file and dfile = os.path.join(dpath,fn_vis) if not os.path.isfile(dfile): return dict() cols, comments = DataIO.readCols(dfile,return_comments=1) comments = [comment for comment in comments if comment] if 'visibility' in fn_vis: xtype = 'wavelength' seltype = 'baseline' elif 'basevis' in fn_vis: xtype = 'baseline' seltype = 'wavelength' model = dict() model[xtype] = cols[0] model['flux'] = cols[1] model[seltype] = dict() for i,comment in enumerate(comments[2:]): val = float(comment.partition(seltype)[2].partition(',')[0]) model[seltype][val] = cols[2+i] return model
def readTxt(self): ''' Read the txt file. Assumes Tmb flux values in K, with respect to velocity. ''' data = DataIO.readCols(filename=self.fn, start_row=0, nans=1) if self.fn[-6:] == '.ISPEC': del data[0] data[0] = data[0] / 1000. self['contents']['velocity'] = data[0] self['contents']['flux'] = data[1] if self['contents']['velocity'][0] > self['contents']['velocity'][-1]: self['contents']['velocity'] = self['contents']['velocity'][::-1] self['contents']['flux'] = self['contents']['flux'][::-1] self['contents']['date_obs'] = 'N.A.' self['contents']['vlsr'] = None
def readTxt(self): ''' Read the txt file. Assumes Tmb flux values in K, with respect to velocity. ''' data = DataIO.readCols(filename=self.filename,start_row=0,nans=1) if self.filename[-6:] == '.ISPEC': del data[0] data[0] = data[0]/1000. self.contents['velocity'] = data[0] self.contents['flux'] = data[1] if self.contents['velocity'][0] > self.contents['velocity'][-1]: self.contents['velocity'] = self.contents['velocity'][::-1] self.contents['flux'] = self.contents['flux'][::-1] self.contents['date_obs'] = 'N.A.' self.contents['vlsr'] = None
def readLineFit(self, **kwargs): ''' Read the data from the line fit procedure. @keyword kwargs: Extra keywords for the readCols method. (default: dict()) @type kwargs: dict @return: The line fit columns are returned. @rtype: list[array] ''' fn = os.path.join(self.path_instrument,self.star_name,\ self.path_linefit,'lineFitResults') if not self.path_linefit or not os.path.isfile(fn): self.linefit = None return dd = DataIO.readCols(fn, make_array=0, **kwargs) return dd
def readKappas(self, species): """ Read kappas (cm2/g) and Q_ext/a (cm-1) for a dust species from the MCMax INPUT files. This also reads the absorption and scattering kappas separately. @param species: The dust species (from Dust.dat) @type species: string """ if self.waves.has_key(species): return try: ispecies = self.lspecies.index(species) except ValueError: print "Species not found in Dust.dat." return fn = os.path.join(cc.path.mopac, self.lfilenames[ispecies]) sd = self.lspec_dens[ispecies] if fn[-9:] == ".particle": part_file = DataIO.readFile(filename=fn, delimiter=" ") wav = array([float(q[0]) for q in part_file if len(q) == 4]) kappa = [ array([float(q[1]) for q in part_file if len(q) == 4]), array([float(q[2]) for q in part_file if len(q) == 4]), array([float(q[3]) for q in part_file if len(q) == 4]), ] else: part_file = DataIO.readCols(filename=fn) wav = part_file[0] kappa = part_file[1:] self.spec_dens[species] = sd self.fns[species] = fn self.waves[species] = wav self.kappas[species] = kappa self.qext_a[species] = array(kappa) * 4 / 3.0 * sd
def parseImpact(self): ''' Parse sphinx file 1, line intensities at line center (!) as a function of impact parameter. The output is stored in dict self['sph1']. Note that the headers of the sph1 file state the last two columns are summed intensities. This is not true! It is the intensity at line center. ''' self['sph1'] = dict() data = DataIO.readCols(self.fn.replace('*','1'),start_row=1) self['sph1']['p'] = data[0] self['sph1']['norm_intens'] = data[1] self['sph1']['weighted_intens'] = data[2] self['sph1']['weighted_intens_p^-2'] = data[3] self['sph1']['intens'] = data[4]
def readLineFit(self,**kwargs): ''' Read the data from the line fit procedure. @keyword kwargs: Extra keywords for the readCols method. (default: dict()) @type kwargs: dict @return: The line fit columns are returned. @rtype: list[array] ''' fn = os.path.join(self.path_instrument,self.star_name,\ self.path_linefit,'lineFitResults') if not self.path_linefit or not os.path.isfile(fn): self.linefit = None return dd = DataIO.readCols(fn,make_array=0,**kwargs) return dd
def readKappas(self,species): """ Read kappas (cm2/g) and Q_ext/a (cm-1) for a dust species from the MCMax INPUT files. This also reads the absorption and scattering kappas separately. @param species: The dust species (from Dust.dat) @type species: string """ if self.waves.has_key(species): return try: ispecies = self.lspecies.index(species) except ValueError: print 'Species not found in Dust.dat.' return fn = os.path.join(cc.path.mopac,self.lfilenames[ispecies]) sd = self.lspec_dens[ispecies] if fn[-9:] == '.particle': part_file = DataIO.readFile(filename=fn,delimiter=' ') wav = array([float(q[0]) for q in part_file if len(q) == 4]) kappa = [array([float(q[1]) for q in part_file if len(q) == 4]), array([float(q[2]) for q in part_file if len(q) == 4]), array([float(q[3]) for q in part_file if len(q) == 4])] else: part_file = DataIO.readCols(filename=fn) wav = part_file[0] kappa = part_file[1:] self.waves[species] = wav self.kappas[species] = kappa self.qext_a[species] = array(kappa) * 4/3. * sd
def makeDict(self, path=None): ''' Return a dict with molecule string, and other relevant parameters. @keyword path: If a different path is needed, it can be passed here, for files. For instance, when making dictionaries for Molecule() objects in the case of supercomputer copies. (default: None) @type path: string ''' new_dict = dict([('MOLECULE',str(self).replace('MOLECULE=','')),\ ('ITERA',self.itera),\ ('ABUN_MOLEC',self.abun_molec),\ ('ABUN_MOLEC_RINNER',self.abun_molec_rinner),\ ('ABUN_MOLEC_RE', self.abun_molec_re),\ ('RMAX_MOLEC',self.rmax_molec),\ ('LTE_REQUEST',self.lte_request),\ ('OUTER_R_MODE',self.outer_r_mode),\ ('USE_COLLIS_RADIAT_SWITCH',self.use_collis_radiat_switch),\ ('R_OUTER',self.r_outer)]) for par,isot in [('RATIO_16O_TO_18O','18O'),\ ('RATIO_16O_TO_17O','17O'),\ ('RATIO_12C_TO_13C','13C'),\ ('OPR','p1H')]: if isot in self.molecule: new_dict[par] = getattr(self, par.lower()) if self.dust_to_gas_change_ml_sp: new_dict['CHANGE_DUST_TO_GAS_FOR_ML_SP'] \ = 1 new_dict['DUST_TO_GAS_CHANGE_ML_SP'] \ = self.dust_to_gas_change_ml_sp if self.enhance_abundance_factor: new_dict['ENHANCE_ABUNDANCE_FACTOR'] \ = self.enhance_abundance_factor new_dict['NUMBER_INPUT_ABUNDANCE_VALUES'] \ = len(DataIO.readCols(filename=self.abundance_filename)[0]) new_dict['KEYWORD_TABLE'] = 1 new_dict['MOLECULE_TABLE'] \ = self.molecule_full[:self.molecule_full.index('.')] new_dict['ISOTOPE_TABLE'] = self.molecule_full if path <> None: new_dict['ABUNDANCE_FILENAME'] \ = '"%s"'%os.path.join(path,\ os.path.split(self.abundance_filename)[1]) else: new_dict['ABUNDANCE_FILENAME'] \ = '"%s"'%self.abundance_filename if self.set_keyword_change_abundance: new_dict['SET_KEYWORD_CHANGE_ABUNDANCE'] \ = self.set_keyword_change_abundance if path <> None: new_dict['CHANGE_FRACTION_FILENAME'] \ = '"%s"'%os.path.join(path,\ os.path.split(self.change_fraction_filename)[1]) else: new_dict['CHANGE_FRACTION_FILENAME'] \ = '"%s"'%self.change_fraction_filename if self.set_keyword_change_temperature: new_dict['SET_KEYWORD_CHANGE_TEMPERATURE'] \ = self.set_keyword_change_temperature if path <> None: new_dict['NEW_TEMPERATURE_FILENAME'] \ = '"%s"'%os.path.join(path,\ os.path.split(self.new_temperature_filename)[1]) else: new_dict['NEW_TEMPERATURE_FILENAME'] \ = '"%s"'%self.new_temperature_filename if self.starfile: new_dict['USE_STARFILE'] = 1 if path <> None: starfile = os.path.join(path, os.path.split(self.starfile)[1]) new_dict['STARFILE'] = '"%s"' % starfile else: new_dict['STARFILE'] = '"%s"' % self.starfile return new_dict
def __read(self): ''' Read the MOLECULE_radiat file and set a dictionary for the instance with all the information. Done on creation of an instance of the class. ''' radiat = DataIO.readCols(filename=self.filename, make_array=0)[0] self.dict = {} #- The starting indices for the next step are determined incrementally, #- with a right term added + arbitrary number of zeroes at the end of #- each step in the loop step_sizes = [ self.molecule.nline, self.molecule.nline, self.molecule.ny_up + self.molecule.ny_low, self.molecule.ny_up + self.molecule.ny_low, self.molecule.nline, self.molecule.nline ] if sum(step_sizes) == len(radiat): nozeroes = True else: nozeroes = False #- The ending indices are always the starting index + this index pars = ['EINSTEIN', 'FREQUENCY', 'WEIGHT', 'ENERGY', 'LOWER', 'UPPER'] i = 0 for delta, par in zip(step_sizes, pars): #- grab list from input self.dict[par] = radiat[i:i + delta] #- Determine starting index of the next list try: #- if no zeroes present: don't look for zeroes #- (important cuz an energy level may be zero) i = nozeroes \ and (i + delta) \ or DataIO.findNumber(i+delta,radiat) except IndexError: if par == 'UPPER': #- This is the last step, you expect an index error here, #- since it's the end of the file if not (len(radiat) == i + delta or radiat[i + delta] == 0): #- If the next number is zero, or if the file ends here, #- everything is OK: Move on, otherwise raise error. raise IndexError("When reading %s,"%self.filename + \ "the level indices were " + \ "formatted incorrectly. Aborting...") else: #- Too early to get this error! Aborting! raise IndexError('When reading %s,'%self.filename + \ 'file ended too soon before'+\ ' definition of all parameters. '+\ 'Aborting...') #- if zeroes are present, one ofthe energy levels may also be really zero #- check this and correct for it here. if self.dict['ENERGY'][-1] == 0.0: self.dict['ENERGY'] = self.dict['ENERGY'][0:-1] self.dict['ENERGY'][0:0] = [0.0] #Another check up... if sum(step_sizes) != sum([len(self.dict[par]) for par in pars]): raise IndexError('Fewer or more entries found for all the ' + \ 'parameters in %s than expected. Aborting...'\ %self.filename) self.dict['LOWER'] = [int(x) for x in self.dict['LOWER']] self.dict['UPPER'] = [int(x) for x in self.dict['UPPER']]
def fitLP(filename=None,lprof=None,theory=0,show=0,cfg='',convert_ms_kms=0,\ vary_pars=['vexp'],i_vexp=15.0,i_gamma=1.0,do_gauss=0): ''' Fit a line profile with a soft parabola, and a Gaussian component if required. The method automatically checks if a second component is needed (eg an extra absorption component). An estimate of the expansion velocity (width of the profile) and an improved guess of the vlsr are given. A guess for the gas terminal velocity is returned, as well as its error and the fitted profile (sp/gaussian, and if applicable extra gaussian and the full fit). @keyword filename: The filename to the data file of the line profile. If None a line profile object is expected. (default: None) @type filename: string @keyword lprof: A line profile object (LPDataReader or inheriting classes) If None, a filename is expected! If not None, the results are saved in this object as well as returned upon method call (default: None) @type lprof: LPDataReader() @keyword convert_ms_kms: Convert velocity grid from m/s to km/s. (default: 0) @type convert_ms_kms: bool @keyword theory: If theoretical profile, and filename is given, set vlsr to 0 and read two columns. lprof not relevant if True. (default: 0) @type theory: bool @keyword vary_pars: The soft parabola parameters varied (can only be vexp or gamma for now). The initial values for parameters listed here are not used. If 'gamma' is requested, a reasonable guess for i_vexp when calling the method will improve the fitting results. This is done for the first guess only! If a Gaussian absorption is present improving these first guesses won't make much of a difference. However, the first guess value for gamma is still used. Vexp is always varied if absorption is present. (default: ['vexp']) @type vary_pars: list[string] @keyword i_vexp: The initial guess for the expansion velocity. Not relevant if vexp is included in vary_pars. (default: 15.0) @type i_vexp: float @keyword i_gamma: The initial guess for the gamma parameter of soft parab. Not relevant if gamma is included in vary_pars. (default: 1.0) @type i_gamma: float @keyword do_gauss: Force a Gaussian fit regardless of soft parabola fit results. Still does the soft parabola fit first to allow for comparison of parameters. (default: 0) @type do_gauss: bool @keyword show: Show the results of the fit (default: 0) @type show: bool @return: dictionary including [vexp,evexp,gamma,egamma,fitprof,gaussian,\ fullfit,dintint,fgintint] @rtype: dict[float,float,float,float,funclib.Function(),\ funclib.Function(),funclib.Function()] ''' print '*******************************************' if theory and filename <> None: d = DataIO.readCols(filename=filename) vel = d[0] flux = d[1] vlsr = 0.0 else: if filename is None: filename = lprof.filename print '** Fitting line profile in %s.' % filename if lprof is None: lprof = readLineProfile(filename) vel = lprof.getVelocity() flux = lprof.getFlux() vel = vel[-np.isnan(flux)] flux = flux[-np.isnan(flux)] vlsr = lprof.getVlsr() if convert_ms_kms: vel = vel / 1000. #-- Initial values: [peak tmb,vlsr,vexp,gamma] # For the central peak value, get a first guess from the data # Attempt multiple vexp values and return the best fitting case. # The initial values are given, with an arbitrary value for the vexp key i_mid = argmin(np.abs(vel - vlsr)) peak = mean(flux[i_mid - 2:i_mid + 3]) #-- Vary vexp or gamma if requested. If not requested i_vexp or i_gamma are # used. # Multiple values for gamma are tried and the best fitting model # is then chosen based on the relative error of the fitted gamma. if 'gamma' in vary_pars: igammas = array([-0.5, -0.1, 0.1, 0.5, 1.0, 2.0, 4.0]) firstguess = varyInitialFit(vel,flux,[peak,vlsr,i_vexp,0.0],index=3,\ values=igammas,vary_window=1,vary=[1,1,1,1],\ function=funclib.soft_parabola) i_gamma = firstguess.get_parameters()[0][3] #-- varyInitialFit adapts the velocity window itself. No more # assumptions needed for the expansion velocity ivexps = array([50., 40., 30., 25., 20., 15., 10.]) if 'vexp' in vary_pars: firstguess = varyInitialFit(vel,flux,[peak,vlsr,0.,i_gamma],index=2,\ values=ivexps,vary_window=1,vary=[1,1,1,1],\ function=funclib.soft_parabola) vexp = abs(firstguess.get_parameters()[0][2]) window = 2. print 'First guess fit, using a soft parabola:' print firstguess.param2str(accuracy=5) #-- If vexp > 100, replace with 50. This value is unrealistic, and might be # improved with an extra Gaussian. If not, it will be replaced with a # full Gaussian fit anyway if vexp > 100: vexp = 50. #-- Check whether irregularities are present in the profile. # Initial parameters for a gaussian are returned if true. # For this, a broad selection is made of the profile, to allow for a # decent noise determination outside the line profile keep = np.abs(vel - vlsr) <= (2 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] include_gauss = checkLPShape(velsel, fluxsel, vlsr, vexp, window, show=show) #-- Do the fit of the line again, including an extra gaussian if # irregularities are present. if include_gauss <> None: #-- fit soft para model + gaussian # 1. Set up new soft parabola for several guesses of vexp ivexps = list(ivexps) initial = [peak, vlsr, 0., i_gamma] all_init = [[p] * len(ivexps) for i, p in enumerate(initial) if i != 2] all_init.insert(2, ivexps) functions = [funclib.soft_parabola() for i in ivexps] [ ff.setup_parameters(values=initi) for ff, initi in zip(functions, zip(*all_init)) ] # 2. setup gaussian gaussians = [funclib.gauss() for ff in functions] # initial guesses assuming an interstellar absorption line from the # checkLPShape method [ gg.setup_parameters(values=include_gauss, vary=[True, True, True, False]) for gg in gaussians ] # 3. combine soft para + gaussian, and minimize fit mymodels = [ fit.Model(functions=[ff, gg]) for ff, gg in zip(functions, gaussians) ] [fit.minimize(vel[np.abs(vel-vlsr)<=(init[2]*1.5)],\ flux[np.abs(vel-vlsr)<=(init[2]*1.5)],\ mymodel) for mymodel,init in zip(mymodels,zip(*all_init))] # 4. Select the best fitting result based on the error on vexp mymodels = [fg for fg in mymodels if fg.get_parameters()[1][2] != 0.] functions = [ ff for fg, ff in zip(mymodels, functions) if fg.get_parameters()[1][2] != 0. ] gaussians = [ gg for fg, gg in zip(mymodels, gaussians) if fg.get_parameters()[1][2] != 0. ] fitvalues = array([fg.get_parameters()[0][2] for fg in mymodels]) fiterrors = array([fg.get_parameters()[1][2] for fg in mymodels]) mymodel = mymodels[argmin(abs(fiterrors / fitvalues))] finalfit = functions[argmin(abs(fiterrors / fitvalues))] gaussian = gaussians[argmin(abs(fiterrors / fitvalues))] print 'Improved fit, including extra Gaussian:' print mymodel.param2str(accuracy=5) else: #-- if gamma is requested to be varied, allow another iteration on # gamma with the best vexp guess we already have. if 'gamma' in vary_pars: finalfit = varyInitialFit(vel,flux,[peak,vlsr,vexp,0.0],\ index=3,values=igammas,vary_window=1,\ function=funclib.soft_parabola,\ vary=[True,True,True,True]) print 'Final fit with soft parabola, second gamma iteration:' print finalfit.param2str(accuracy=5) #-- firstguess is best we can do at the moment else: finalfit = firstguess #-- If the relative error on vexp is larger than 30%, usually something # funky is going on in the emission line. Try a Gaussian instead. fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] vexp = abs(finalfit.get_parameters()[0][2]) evexp = abs(finalfit.get_parameters()[1][2]) gamma = finalfit.get_parameters()[0][3] egamma = finalfit.get_parameters()[1][3] #-- Gamma has to be positive. If it isnt, dont bother with Gaussian # (double peaked line profile will not be fitted well with a Gaussian!) if (evexp/vexp > 0.40 and gamma > 0) or (evexp/vexp > 0.20 and vexp> 30.) \ or do_gauss: #-- Go back to default window to try a Gaussian fit #keep = np.abs(vel-vlsr)<=(80) #velselg,fluxselg = vel[keep],flux[keep] do_gauss = 1 include_gauss = None #-- FWHM is twice vexp! sigmas = 2 * ivexps / (2. * sqrt(2. * log(2.))) finalfit = varyInitialFit(vel,flux,[peak,vlsr,0.,0.],index=2,\ values=sigmas,function=funclib.gauss,\ vary_window=1,vary=[True,True,True,False]) vexp = abs( finalfit.get_parameters()[0][2]) * (2. * sqrt(2. * log(2.))) / 2. evexp = abs( finalfit.get_parameters()[1][2]) * (2. * sqrt(2. * log(2.))) / 2. fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] gamma, egamma = None, None window = 3. print 'Improved fit, using a gaussian instead of soft parabola:' print finalfit.param2str(accuracy=5) #-- Compute numerical integrations. # After fitting, window for integration should be 0.6*window. vexp is # not expected to be too small anymore as in checkLPShape keep = np.abs(vel - vlsr) <= (0.6 * window * vexp) velsel = vel[keep] flux_first = firstguess.evaluate(velsel) flux_final = finalfit.evaluate(velsel) dimb = trapz(y=flux[keep], x=velsel) fi_final = trapz(y=flux_final, x=velsel) print('I_mb (emission line data): %f'\ %dimb) print('I_mb (SP -- initial guess): %f'\ %trapz(y=flux_first,x=velsel)) print('I_mb (SP -- final guess): %f'\ %fi_final) if include_gauss <> None: fitted_flux = mymodel.evaluate(velsel) print('I_mb (SP + Gauss fit): %f'\ %trapz(y=fitted_flux,x=velsel)) print('Final v_exp guess: %.4f +/- %.4f km/s' % (vexp, evexp)) if gamma <> None: print('Final gamma guess: %.4f +/- %.4f' % (gamma, egamma)) print('Final vlsr guess: %.4f +/- %.4f' % (fvlsr, fevlsr)) fwhm = getLPDataFWHM(lprof) print('The FWHM is %.2f km/s.' % (fwhm)) #-- plot if show or cfg: plt.clf() #-- improve velocity window for plotting keep = np.abs(vel - vlsr) <= (1.5 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] vel_highres = np.linspace(velsel[0], velsel[-1], 10000) flux_final_highres = finalfit.evaluate(vel_highres) flux_first_highres = firstguess.evaluate(vel_highres) if include_gauss <> None: flux_full_highres = mymodel.evaluate(vel_highres) if show: plt.step(velsel,fluxsel,'-r',where='mid',lw=3,\ label='Observed profile') plt.plot(vel_highres,flux_first_highres,'b-',lw=3,\ label='First guess') plt.plot(vel_highres,flux_final_highres,'g--',lw=3,\ label='Improved guess') if include_gauss <> None: plt.plot(vel_highres,flux_full_highres,'g-',lw=2,\ label='Full fit (including Gaussian)') leg = plt.legend(loc='best', fancybox=True) leg.get_frame().set_alpha(0.5) plt.show() if cfg: pf = '%s_fitted_%s'%(do_gauss and 'gaussFit' or 'SPFit',\ os.path.split(filename)[1]) keytags = ['Observed profile', 'Improved guess'] line_types = [ '-r', '-b', ] x = [velsel, vel_highres] y = [fluxsel, flux_final_highres] if include_gauss <> None: line_types.append('g--') x.append(vel_highres) y.append(flux_full_highres) keytags.append('Full fit (including Gaussian)') pf = Plotting2.plotCols(x=x,y=y,filename=pf,cfg=cfg,linewidth=5,\ yaxis='$T_\mathrm{mb}\ (\mathrm{K})$',\ xaxis='$v (\mathrm{km}/\mathrm{s})$',\ keytags=keytags,line_types=line_types,\ histoplot=[0]) print 'Your figure can be found at %s .' % pf #-- Collecting all relevant results and returning. results = dict() #-- If a Gaussian was used for the main profile fit results['do_gauss'] = do_gauss #-- Fitted parameters and errors results['vlsr'] = fvlsr results['evlsr'] = fevlsr results['vexp'] = vexp results['evexp'] = evexp results['fwhm'] = fwhm #-- Gamma is None if no soft parabola was fitted results['gamma'] = gamma results['egamma'] = egamma #-- Integrated line strengths: main line fit, data themselves, fit window results['fgintint'] = fi_final results['dintint'] = dimb results['intwindow'] = window * 0.6 #-- Saving parameters for later evaluation. Full fit is accessible by # making the functions separately and setting pars, then using fit.Model results['fitprof'] = (do_gauss and 'gauss' or 'soft_parabola',\ list(finalfit.get_parameters()[0])) if include_gauss <> None: results['fitabs'] = ('gauss', list(gaussian.get_parameters()[0])) else: results['fitabs'] = None return results
def readDustInfo(self): """ Read all column densities, min/max temperatures and min/max radii for the species involved in the MCMax model. Note that the self.coldens dictionary does not give real column densities! This dict merely gives column densities in a prescribed shell with given min and max radius, in order to compare with the H2 col density. """ dens = self.star.getDustDensity() temp = self.star.getDustTemperature() compf = os.path.join(cc.path.mcmax,self.star.path_mcmax,'models',\ self.star['LAST_MCMAX_MODEL'],'composition.dat') comp = DataIO.readCols(compf) self.rad = comp.pop(0) * self.au self.r_outer = self.rad[-1] for species in self.star.getDustList(): #- Save the actual density profile for this dust species, as well #- as calculating the full column density of a dust species. self.dustfractions[species] = comp.pop(0) self.compd[species] = self.dustfractions[species] * dens self.fullcoldens[species] = trapz(x=self.rad, y=self.compd[species]) #- Determine the column density from 90% of the dust species formed #- onward, based on the mass fractions! #- Not before, because the comparison with H2 must be made, #- and this will skew the result if not solely looking at where the #- dust has (almost) all been formed. #- We also save min amd max radii, for use with the H2 calculation a_species = self.star['A_%s' % species] maxdens = max(self.compd[species]) mindens = maxdens * 10**(-10) radsel = self.rad[(self.dustfractions[species]>0.9*a_species)*\ (self.compd[species]>mindens)] denssel = self.compd[species]\ [(self.dustfractions[species]>0.9*a_species)*\ (self.compd[species]>mindens)] self.coldens[species] = trapz(x=radsel, y=denssel) if radsel.size: self.r_min_cd[species] = radsel[0] self.r_max_cd[species] = radsel[-1] else: print 'Threshold dust mass fraction not reached for %s.' % species self.r_min_cd[species] = 0 self.r_max_cd[species] = 0 #- Determine the actual destruction radius and temperature. #- Taken where the density reaches 1% of the maximum density #- (not mass fraction). self.r_des[species] = self.rad[self.compd[species] > (maxdens * 0.01)][0] self.t_des[species] = temp[self.compd[species] > (maxdens * 0.01)][0] #- e-10 as limit for minimum is ok, because if shell is 100000 R* #- the mass conservation dictates ~ (10^5)^2 = 10^10 (r^2 law) #- decrease in density. Shells this big dont occur anyway. self.r_max[species] = self.rad[self.compd[species] > mindens][-1] self.t_min[species] = temp[self.compd[species] > mindens][-1]
def fitLP( filename=None, lprof=None, theory=0, show=0, cfg="", convert_ms_kms=0, vary_pars=["vexp"], i_vexp=15.0, i_gamma=1.0, do_gauss=0, ): """ Fit a line profile with a soft parabola, and a Gaussian component if required. The method automatically checks if a second component is needed (eg an extra absorption component). An estimate of the expansion velocity (width of the profile) and an improved guess of the vlsr are given. A guess for the gas terminal velocity is returned, as well as its error and the fitted profile (sp/gaussian, and if applicable extra gaussian and the full fit). @keyword filename: The filename to the data file of the line profile. If None a line profile object is expected. (default: None) @type filename: string @keyword lprof: A line profile object (LPDataReader or inheriting classes) If None, a filename is expected! If not None, the results are saved in this object as well as returned upon method call (default: None) @type lprof: LPDataReader() @keyword convert_ms_kms: Convert velocity grid from m/s to km/s. (default: 0) @type convert_ms_kms: bool @keyword theory: If theoretical profile, and filename is given, set vlsr to 0 and read two columns. lprof not relevant if True. (default: 0) @type theory: bool @keyword vary_pars: The soft parabola parameters varied (can only be vexp or gamma for now). The initial values for parameters listed here are not used. If 'gamma' is requested, a reasonable guess for i_vexp when calling the method will improve the fitting results. This is done for the first guess only! If a Gaussian absorption is present improving these first guesses won't make much of a difference. However, the first guess value for gamma is still used. Vexp is always varied if absorption is present. (default: ['vexp']) @type vary_pars: list[string] @keyword i_vexp: The initial guess for the expansion velocity. Not relevant if vexp is included in vary_pars. (default: 15.0) @type i_vexp: float @keyword i_gamma: The initial guess for the gamma parameter of soft parab. Not relevant if gamma is included in vary_pars. (default: 1.0) @type i_gamma: float @keyword do_gauss: Force a Gaussian fit regardless of soft parabola fit results. Still does the soft parabola fit first to allow for comparison of parameters. (default: 0) @type do_gauss: bool @keyword show: Show the results of the fit (default: 0) @type show: bool @return: dictionary including [vexp,evexp,gamma,egamma,fitprof,gaussian,\ fullfit,dintint,fgintint] @rtype: dict[float,float,float,float,funclib.Function(),\ funclib.Function(),funclib.Function()] """ print "*******************************************" if theory and filename <> None: d = DataIO.readCols(filename=filename) vel = d[0] flux = d[1] vlsr = 0.0 else: if filename is None: filename = lprof.filename print "** Fitting line profile in %s." % filename if lprof is None: lprof = readLineProfile(filename) vel = lprof.getVelocity() flux = lprof.getFlux() vel = vel[-np.isnan(flux)] flux = flux[-np.isnan(flux)] vlsr = lprof.getVlsr() if convert_ms_kms: vel = vel / 1000.0 # -- Initial values: [peak tmb,vlsr,vexp,gamma] # For the central peak value, get a first guess from the data # Attempt multiple vexp values and return the best fitting case. # The initial values are given, with an arbitrary value for the vexp key i_mid = argmin(np.abs(vel - vlsr)) peak = mean(flux[i_mid - 2 : i_mid + 3]) # -- Vary vexp or gamma if requested. If not requested i_vexp or i_gamma are # used. # Multiple values for gamma are tried and the best fitting model # is then chosen based on the relative error of the fitted gamma. if "gamma" in vary_pars: igammas = array([-0.5, -0.1, 0.1, 0.5, 1.0, 2.0, 4.0]) firstguess = varyInitialFit( vel, flux, [peak, vlsr, i_vexp, 0.0], index=3, values=igammas, vary_window=1, vary=[1, 1, 1, 1], function=funclib.soft_parabola, ) i_gamma = firstguess.get_parameters()[0][3] # -- varyInitialFit adapts the velocity window itself. No more # assumptions needed for the expansion velocity ivexps = array([50.0, 40.0, 30.0, 25.0, 20.0, 15.0, 10.0]) if "vexp" in vary_pars: firstguess = varyInitialFit( vel, flux, [peak, vlsr, 0.0, i_gamma], index=2, values=ivexps, vary_window=1, vary=[1, 1, 1, 1], function=funclib.soft_parabola, ) vexp = abs(firstguess.get_parameters()[0][2]) window = 2.0 print "First guess fit, using a soft parabola:" print firstguess.param2str(accuracy=5) # -- If vexp > 100, replace with 50. This value is unrealistic, and might be # improved with an extra Gaussian. If not, it will be replaced with a # full Gaussian fit anyway if vexp > 100: vexp = 50.0 # -- Check whether irregularities are present in the profile. # Initial parameters for a gaussian are returned if true. # For this, a broad selection is made of the profile, to allow for a # decent noise determination outside the line profile keep = np.abs(vel - vlsr) <= (2 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] include_gauss = checkLPShape(velsel, fluxsel, vlsr, vexp, window, show=show) # -- Do the fit of the line again, including an extra gaussian if # irregularities are present. if include_gauss <> None: # -- fit soft para model + gaussian # 1. Set up new soft parabola for several guesses of vexp ivexps = list(ivexps) initial = [peak, vlsr, 0.0, i_gamma] all_init = [[p] * len(ivexps) for i, p in enumerate(initial) if i != 2] all_init.insert(2, ivexps) functions = [funclib.soft_parabola() for i in ivexps] [ff.setup_parameters(values=initi) for ff, initi in zip(functions, zip(*all_init))] # 2. setup gaussian gaussians = [funclib.gauss() for ff in functions] # initial guesses assuming an interstellar absorption line from the # checkLPShape method [gg.setup_parameters(values=include_gauss, vary=[True, True, True, False]) for gg in gaussians] # 3. combine soft para + gaussian, and minimize fit mymodels = [fit.Model(functions=[ff, gg]) for ff, gg in zip(functions, gaussians)] [ fit.minimize( vel[np.abs(vel - vlsr) <= (init[2] * 1.5)], flux[np.abs(vel - vlsr) <= (init[2] * 1.5)], mymodel ) for mymodel, init in zip(mymodels, zip(*all_init)) ] # 4. Select the best fitting result based on the error on vexp mymodels = [fg for fg in mymodels if fg.get_parameters()[1][2] != 0.0] functions = [ff for fg, ff in zip(mymodels, functions) if fg.get_parameters()[1][2] != 0.0] gaussians = [gg for fg, gg in zip(mymodels, gaussians) if fg.get_parameters()[1][2] != 0.0] fitvalues = array([fg.get_parameters()[0][2] for fg in mymodels]) fiterrors = array([fg.get_parameters()[1][2] for fg in mymodels]) mymodel = mymodels[argmin(abs(fiterrors / fitvalues))] finalfit = functions[argmin(abs(fiterrors / fitvalues))] gaussian = gaussians[argmin(abs(fiterrors / fitvalues))] print "Improved fit, including extra Gaussian:" print mymodel.param2str(accuracy=5) else: # -- if gamma is requested to be varied, allow another iteration on # gamma with the best vexp guess we already have. if "gamma" in vary_pars: finalfit = varyInitialFit( vel, flux, [peak, vlsr, vexp, 0.0], index=3, values=igammas, vary_window=1, function=funclib.soft_parabola, vary=[True, True, True, True], ) print "Final fit with soft parabola, second gamma iteration:" print finalfit.param2str(accuracy=5) # -- firstguess is best we can do at the moment else: finalfit = firstguess # -- If the relative error on vexp is larger than 30%, usually something # funky is going on in the emission line. Try a Gaussian instead. fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] vexp = abs(finalfit.get_parameters()[0][2]) evexp = abs(finalfit.get_parameters()[1][2]) gamma = finalfit.get_parameters()[0][3] egamma = finalfit.get_parameters()[1][3] # -- Gamma has to be positive. If it isnt, dont bother with Gaussian # (double peaked line profile will not be fitted well with a Gaussian!) if (evexp / vexp > 0.40 and gamma > 0) or (evexp / vexp > 0.20 and vexp > 30.0) or do_gauss: # -- Go back to default window to try a Gaussian fit # keep = np.abs(vel-vlsr)<=(80) # velselg,fluxselg = vel[keep],flux[keep] do_gauss = 1 include_gauss = None # -- FWHM is twice vexp! sigmas = 2 * ivexps / (2.0 * sqrt(2.0 * log(2.0))) finalfit = varyInitialFit( vel, flux, [peak, vlsr, 0.0, 0.0], index=2, values=sigmas, function=funclib.gauss, vary_window=1, vary=[True, True, True, False], ) vexp = abs(finalfit.get_parameters()[0][2]) * (2.0 * sqrt(2.0 * log(2.0))) / 2.0 evexp = abs(finalfit.get_parameters()[1][2]) * (2.0 * sqrt(2.0 * log(2.0))) / 2.0 fvlsr = finalfit.get_parameters()[0][1] fevlsr = finalfit.get_parameters()[1][1] gamma, egamma = None, None window = 3.0 print "Improved fit, using a gaussian instead of soft parabola:" print finalfit.param2str(accuracy=5) # -- Compute numerical integrations. # After fitting, window for integration should be 0.6*window. vexp is # not expected to be too small anymore as in checkLPShape keep = np.abs(vel - vlsr) <= (0.6 * window * vexp) velsel = vel[keep] flux_first = firstguess.evaluate(velsel) flux_final = finalfit.evaluate(velsel) dimb = trapz(y=flux[keep], x=velsel) fi_final = trapz(y=flux_final, x=velsel) print ("I_mb (emission line data): %f" % dimb) print ("I_mb (SP -- initial guess): %f" % trapz(y=flux_first, x=velsel)) print ("I_mb (SP -- final guess): %f" % fi_final) if include_gauss <> None: fitted_flux = mymodel.evaluate(velsel) print ("I_mb (SP + Gauss fit): %f" % trapz(y=fitted_flux, x=velsel)) print ("Final v_exp guess: %.4f +/- %.4f km/s" % (vexp, evexp)) if gamma <> None: print ("Final gamma guess: %.4f +/- %.4f" % (gamma, egamma)) print ("Final vlsr guess: %.4f +/- %.4f" % (fvlsr, fevlsr)) fwhm = getLPDataFWHM(lprof) print ("The FWHM is %.2f km/s." % (fwhm)) # -- plot if show or cfg: plt.clf() # -- improve velocity window for plotting keep = np.abs(vel - vlsr) <= (1.5 * window * vexp) velsel, fluxsel = vel[keep], flux[keep] vel_highres = np.linspace(velsel[0], velsel[-1], 10000) flux_final_highres = finalfit.evaluate(vel_highres) flux_first_highres = firstguess.evaluate(vel_highres) if include_gauss <> None: flux_full_highres = mymodel.evaluate(vel_highres) if show: plt.step(velsel, fluxsel, "-r", where="mid", lw=3, label="Observed profile") plt.plot(vel_highres, flux_first_highres, "b-", lw=3, label="First guess") plt.plot(vel_highres, flux_final_highres, "g--", lw=3, label="Improved guess") if include_gauss <> None: plt.plot(vel_highres, flux_full_highres, "g-", lw=2, label="Full fit (including Gaussian)") leg = plt.legend(loc="best", fancybox=True) leg.get_frame().set_alpha(0.5) plt.show() if cfg: pf = "%s_fitted_%s" % (do_gauss and "gaussFit" or "SPFit", os.path.split(filename)[1]) keytags = ["Observed profile", "Improved guess"] line_types = ["-r", "-b"] x = [velsel, vel_highres] y = [fluxsel, flux_final_highres] if include_gauss <> None: line_types.append("g--") x.append(vel_highres) y.append(flux_full_highres) keytags.append("Full fit (including Gaussian)") pf = Plotting2.plotCols( x=x, y=y, filename=pf, cfg=cfg, linewidth=5, yaxis="$T_\mathrm{mb}\ (\mathrm{K})$", xaxis="$v (\mathrm{km}/\mathrm{s})$", keytags=keytags, line_types=line_types, histoplot=[0], ) print "Your figure can be found at %s ." % pf # -- Collecting all relevant results and returning. results = dict() # -- If a Gaussian was used for the main profile fit results["do_gauss"] = do_gauss # -- Fitted parameters and errors results["vlsr"] = fvlsr results["evlsr"] = fevlsr results["vexp"] = vexp results["evexp"] = evexp results["fwhm"] = fwhm # -- Gamma is None if no soft parabola was fitted results["gamma"] = gamma results["egamma"] = egamma # -- Integrated line strengths: main line fit, data themselves, fit window results["fgintint"] = fi_final results["dintint"] = dimb results["intwindow"] = window * 0.6 # -- Saving parameters for later evaluation. Full fit is accessible by # making the functions separately and setting pars, then using fit.Model results["fitprof"] = (do_gauss and "gauss" or "soft_parabola", list(finalfit.get_parameters()[0])) if include_gauss <> None: results["fitabs"] = ("gauss", list(gaussian.get_parameters()[0])) else: results["fitabs"] = None return results
def makeDict(self,path=None,in_progress=0): ''' Return a dict with molecule string, and other relevant parameters. @keyword path: If a different path is needed, it can be passed here, for files. For instance, when making dictionaries for Molecule() objects in the case of supercomputer copies. (default: None) @type path: string @keyword in_progress: add an extra dict entry "IN_PROGRESS" if the molecule is still being calculated somewhere. (default: 0) @type in_progress: bool @return: The molecule dictionary including all relevant, defining information @rtype: dict() ''' dd = dict([('MOLECULE',str(self).replace('MOLECULE=','')),\ ('ITERA',self.itera),\ ('ABUN_MOLEC',self.abun_molec),\ ('ABUN_MOLEC_RINNER',self.abun_molec_rinner),\ ('ABUN_MOLEC_RE', self.abun_molec_re),\ ('RMAX_MOLEC',self.rmax_molec),\ ('LTE_REQUEST',self.lte_request),\ ('OUTER_R_MODE',self.outer_r_mode),\ ('USE_COLLIS_RADIAT_SWITCH',self.use_collis_radiat_switch),\ ('R_OUTER',self.r_outer),\ ('USE_NO_MASER_OPTION',self.use_no_maser_option),\ ('USE_MASER_IN_SPHINX',self.use_maser_in_sphinx),\ ('FEHLER',self.fehler),\ ('XDEX',self.xdex),\ ('N_FREQ',self.n_freq),\ ('START_APPROX',self.start_approx),\ ('USE_FRACTION_LEVEL_CORR',self.use_fraction_level_corr),\ ('FRACTION_LEVEL_CORR',self.fraction_level_corr),\ ('NUMBER_LEVEL_MAX_CORR',self.number_level_max_corr) ]) for par,isot in [('RATIO_16O_TO_18O','18O'),\ ('RATIO_16O_TO_17O','17O'),\ ('RATIO_12C_TO_13C','13C'),\ ('OPR','p1H')]: if isot in self.molecule: dd[par] = getattr(self,par.lower()) if self.dust_to_gas_change_ml_sp: dd['CHANGE_DUST_TO_GAS_FOR_ML_SP'] = 1 dd['DUST_TO_GAS_CHANGE_ML_SP'] = self.dust_to_gas_change_ml_sp if self.enhance_abundance_factor: dd['ENHANCE_ABUNDANCE_FACTOR'] = self.enhance_abundance_factor niav = len(DataIO.readCols(filename=self.abundance_filename)[0]) dd['NUMBER_INPUT_ABUNDANCE_VALUES'] = niav dd['KEYWORD_TABLE'] = 1 moltab = self.molecule_full[:self.molecule_full.index('.')] dd['MOLECULE_TABLE'] = moltab dd['ISOTOPE_TABLE'] = self.molecule_full if not path is None: afn = os.path.join(path,\ os.path.split(self.abundance_filename)[1]) else: afn = self.abundance_filename dd['ABUNDANCE_FILENAME'] = '"{}"'.format(afn) if self.set_keyword_change_abundance: dd['SET_KEYWORD_CHANGE_ABUNDANCE'] \ = self.set_keyword_change_abundance if not path is None: cffn = os.path.join(path,\ os.path.split(self.change_fraction_filename)[1]) else: cffn = self.change_fraction_filename dd['CHANGE_FRACTION_FILENAME'] = '"{}"'.format(cffn) if self.set_keyword_change_temperature: dd['SET_KEYWORD_CHANGE_TEMPERATURE'] \ = self.set_keyword_change_temperature if not path is None: tfn = os.path.join(path,\ os.path.split(self.new_temperature_filename)[1]) else: tfn = self.new_temperature_filename dd['NEW_TEMPERATURE_FILENAME'] = '"{}"'.format(tfn) if self.starfile: dd['USE_STARFILE'] = 1 if not path is None: sfn = os.path.join(path,os.path.split(self.starfile)[1]) else: sfn = self.starfile dd['STARFILE'] = '"{}"'.format(sfn) if int(in_progress): dd['IN_PROGRESS'] = 1 return dd
def makeDict(self,path=None): ''' Return a dict with molecule string, and other relevant parameters. @keyword path: If a different path is needed, it can be passed here, for files. For instance, when making dictionaries for Molecule() objects in the case of supercomputer copies. (default: None) @type path: string ''' new_dict = dict([('MOLECULE',str(self).replace('MOLECULE=','')),\ ('ITERA',self.itera),\ ('ABUN_MOLEC',self.abun_molec),\ ('ABUN_MOLEC_RINNER',self.abun_molec_rinner),\ ('ABUN_MOLEC_RE', self.abun_molec_re),\ ('RMAX_MOLEC',self.rmax_molec),\ ('LTE_REQUEST',self.lte_request),\ ('OUTER_R_MODE',self.outer_r_mode),\ ('USE_COLLIS_RADIAT_SWITCH',self.use_collis_radiat_switch),\ ('R_OUTER',self.r_outer)]) for par,isot in [('RATIO_16O_TO_18O','18O'),\ ('RATIO_16O_TO_17O','17O'),\ ('RATIO_12C_TO_13C','13C'),\ ('OPR','p1H')]: if isot in self.molecule: new_dict[par] = getattr(self,par.lower()) if self.dust_to_gas_change_ml_sp: new_dict['CHANGE_DUST_TO_GAS_FOR_ML_SP'] \ = 1 new_dict['DUST_TO_GAS_CHANGE_ML_SP'] \ = self.dust_to_gas_change_ml_sp if self.enhance_abundance_factor: new_dict['ENHANCE_ABUNDANCE_FACTOR'] \ = self.enhance_abundance_factor new_dict['NUMBER_INPUT_ABUNDANCE_VALUES'] \ = len(DataIO.readCols(filename=self.abundance_filename)[0]) new_dict['KEYWORD_TABLE'] = 1 new_dict['MOLECULE_TABLE'] \ = self.molecule_full[:self.molecule_full.index('.')] new_dict['ISOTOPE_TABLE'] = self.molecule_full if path <> None: new_dict['ABUNDANCE_FILENAME'] \ = '"%s"'%os.path.join(path,\ os.path.split(self.abundance_filename)[1]) else: new_dict['ABUNDANCE_FILENAME'] \ = '"%s"'%self.abundance_filename if self.set_keyword_change_abundance: new_dict['SET_KEYWORD_CHANGE_ABUNDANCE'] \ = self.set_keyword_change_abundance if path <> None: new_dict['CHANGE_FRACTION_FILENAME'] \ = '"%s"'%os.path.join(path,\ os.path.split(self.change_fraction_filename)[1]) else: new_dict['CHANGE_FRACTION_FILENAME'] \ = '"%s"'%self.change_fraction_filename if self.set_keyword_change_temperature: new_dict['SET_KEYWORD_CHANGE_TEMPERATURE'] \ = self.set_keyword_change_temperature if path <> None: new_dict['NEW_TEMPERATURE_FILENAME'] \ = '"%s"'%os.path.join(path,\ os.path.split(self.new_temperature_filename)[1]) else: new_dict['NEW_TEMPERATURE_FILENAME'] \ = '"%s"'%self.new_temperature_filename if self.starfile: new_dict['USE_STARFILE'] = 1 if path <> None: starfile = os.path.join(path,os.path.split(self.starfile)[1]) new_dict['STARFILE'] = '"%s"'%starfile else: new_dict['STARFILE'] = '"%s"'%self.starfile return new_dict