def make_chisq_plots(filename, paramfilename, galaxyname, slitmaskname, startstar=0, globular=False): """ Plot chisq contours for stars whose [Mn/H] abundances have already been measured. Inputs: filename -- file with observed spectra paramfilename -- file with parameters of observed spectra galaxyname -- galaxy name, options: 'scl' slitmaskname -- slitmask name, options: 'scl1' Keywords: globular -- if 'False', put into output path of galaxy; else, put into globular cluster path """ # Input filename if globular: file = '/raid/madlr/glob/'+galaxyname+'/'+slitmaskname+'.csv' else: file = '/raid/madlr/dsph/'+galaxyname+'/'+slitmaskname+'.csv' name = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=0, dtype='str') mn = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=8) mnerr = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=9) #dlam = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=8) #dlamerr = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=9) # Get number of stars in file with observed spectra Nstars = open_obs_file(filename) # Plot chi-sq contours for each star for i in range(startstar, Nstars): try: # Check if parameters are measured temp, logg, fe, alpha, fe_err = open_obs_file(filename, retrievespec=i, specparams=True) if np.isclose(1.5,logg) and np.isclose(fe,-1.5) and np.isclose(fe_err, 0.0): print('Bad parameter measurement! Skipped #'+str(i+1)+'/'+str(Nstars)+' stars') continue # Open star star = chi_sq.obsSpectrum(filename, paramfilename, i, False, galaxyname, slitmaskname, globular, 'new') # Check if star has already had [Mn/H] measured if star.specname in name: # If so, plot chi-sq contours if error is < 1 dex idx = np.where(name == star.specname) if mnerr[idx][0] < 1: params0 = [mn[idx][0], mnerr[idx][0]] best_mn, error = star.plot_chisq(params0, minimize=False, plots=True, save=True) except Exception as e: print(repr(e)) print('Skipped star #'+str(i+1)+'/'+str(Nstars)+' stars') continue print('Finished star '+star.specname, '#'+str(i+1)+'/'+str(Nstars)+' stars') return
def prep_run( filename, galaxyname, slitmaskname, membercheck=None, memberlist='/raid/caltech/articles/kirby_gclithium/table_catalog.dat', velmemberlist=None, globular=False): """ Get data in preparation for measuring chi-sq values. Inputs: filename -- file with observed spectra galaxyname -- galaxy name, options: 'scl' slitmaskname -- slitmask name, options: 'scl1' Keywords: membercheck -- do membership check for this object memberlist -- member list (from Evan's Li-rich giants paper) velmemberlist -- member list (from radial velocity check) globular -- if 'False' (default), put into output path of galaxy; else, put into globular cluster path Outputs: Nstars -- total number of stars in the data file RA, Dec -- coordinate arrays for stars in file membernames -- list of all members (based on Evan's Li-rich giants paper and/or velocity check) galaxyname, slitmaskname, filename, membercheck -- from input """ # Prep for member check if membercheck is not None: # Check if stars are in member list from Evan's Li-rich giants paper table = ascii.read(memberlist) memberindex = np.where(table.columns[0] == membercheck) membernames = table.columns[1][memberindex] # Also check if stars are in member list from velocity cut if velmemberlist is not None: oldmembernames = membernames membernames = [] velmembernames = np.genfromtxt(velmemberlist, dtype='str') for i in range(len(oldmembernames)): if oldmembernames[i] in velmembernames: membernames.append(oldmembernames) membernames = np.asarray(membernames) else: membernames = None # Get number of stars in file Nstars = open_obs_file(filename) # Get coordinates of stars in file RA, Dec = open_obs_file(filename, coords=True) return galaxyname, slitmaskname, filename, Nstars, RA, Dec, membercheck, membernames, globular
def mp_worker(i, filename, paramfilename, wvlcorr, galaxyname, slitmaskname, globular, lines, plots, Nstars, RA, Dec, membercheck, membernames, correctionslist, corrections): """ Function to parallelize: chi-sq fitting for a single star """ try: # Get metallicity of star to use for initial guess print('Getting initial metallicity') temp, logg, fe, alpha, fe_err = open_obs_file(filename, retrievespec=i, specparams=True) # Get dlam (FWHM) of star to use for initial guess specname, obswvl, obsflux, ivar, dlam, zrest = open_obs_file( filename, retrievespec=i) # Check for bad parameter measurement if np.isclose(temp, 4750.) and np.isclose(fe, -1.5) and np.isclose( alpha, 0.2): print('Bad parameter measurement! Parameters: ' + str(temp) + ', ' + str(fe) + ', ' + str(alpha) + ' Skipped #' + str(i + 1) + '/' + str(Nstars) + ' stars') return None # Do membership check if membercheck is not None: if specname not in membernames: print('Not in member list! Skipped ' + specname) return None # Vary stellar parameters if correctionslist is not None: if int(specname) in np.asarray(correctionslist['ID']): idx = np.where( np.asarray(correctionslist['ID']) == int(specname))[0] # Vary the quantity required if corrections[0] == 'Teff': colstring = 'Teff' + str(int(np.abs(corrections[2]))) if corrections[1] == 'up': temp = temp + corrections[ 2] # Make the correction go the right direction fe = fe + float( correctionslist['FeH_' + colstring][idx]) else: temp = temp - corrections[2] fe = fe - float( correctionslist['FeH_' + colstring][idx]) elif corrections[0] == 'logg': colstring = 'logg0' + str(int(10 * np.abs(corrections[2]))) if corrections[1] == 'up': logg = logg + corrections[ 2] # Make the correction go the right direction fe = fe - float( correctionslist['FeH_' + colstring][idx]) else: logg = logg - corrections[2] fe = fe + float( correctionslist['FeH_' + colstring][idx]) # Now determine the direction to vary alpha ''' key = corrections[0]+str(corrections[2]) if corrections[3] == 'up': alpha = alpha + float(correctionslist['MgFe_'+key][idx] + 4*correctionslist['SiFe_'+key][idx] + 2*correctionslist['CaFe_'+key][idx] + 6*correctionslist['TiFe_'+key][idx])/13 else: alpha = alpha - float(correctionslist['MgFe_'+key][idx] + 4*correctionslist['SiFe_'+key][idx] + 2*correctionslist['CaFe_'+key][idx] + 6*correctionslist['TiFe_'+key][idx])/13 ''' else: print('No stellar parameter corrections listed!') # Run optimization code star = chi_sq.obsSpectrum(filename, paramfilename, i, wvlcorr, galaxyname, slitmaskname, globular, lines, RA[i], Dec[i], plot=True, specialparams=[temp, logg, fe, alpha]) best_mn, error, finalchisq = star.plot_chisq(fe, output=True, plots=plots) print('Finished star ' + star.specname, '#' + str(i + 1) + '/' + str(Nstars) + ' stars') result = np.array([ star.temp, star.logg, star.fe, star.fe_err, star.alpha, best_mn, error ], dtype=object) for i in range(len(result)): if np.isscalar(result[i]) == False: result[i] = result[i][0] return star.specname, str(RA[i]), str(Dec[i]), str(result[0]), str( result[1]), str(result[2]), str(result[3]), str(result[4]), str( result[5]), str(result[6]), str(finalchisq) except Exception as e: print(repr(e)) print('Skipped star #' + str(i + 1) + '/' + str(Nstars) + ' stars') return None
def __init__(self, obsfilename, paramfilename, starnum, wvlcorr, galaxyname, slitmaskname, globular, lines, RA, Dec, obsspecial=None, plot=False, hires=None, smooth=None, specialparams=None): # Observed star self.obsfilename = obsfilename # File with observed spectra self.paramfilename = paramfilename # File with parameters of observed spectra self.starnum = starnum # Star number self.galaxyname = galaxyname # Name of galaxy self.slitmaskname = slitmaskname # Name of slitmask self.globular = globular # Parameter marking if globular cluster self.lines = lines # Parameter marking whether or not to use revised or original linelist # If observed spectrum comes from moogify file (default), open observed file and continuum normalize as usual if obsspecial is None: # Output filename if self.globular: self.outputname = '/raid/madlr/glob/' + galaxyname + '/' + slitmaskname else: self.outputname = '/raid/madlr/dsph/' + galaxyname + '/' + slitmaskname # Open observed spectrum self.specname, self.obswvl, self.obsflux, self.ivar, self.dlam, self.zrest = open_obs_file( self.obsfilename, retrievespec=self.starnum) # Get measured parameters from observed spectrum self.temp, self.logg, self.fe, self.alpha, self.fe_err = open_obs_file( self.paramfilename, self.starnum, specparams=True, objname=self.specname, inputcoords=[RA, Dec]) if specialparams is not None: self.temp = specialparams[0] self.logg = specialparams[1] self.fe = specialparams[2] self.alpha = specialparams[3] if plot: # Plot observed spectrum plt.figure() plt.plot(self.obswvl, self.obsflux, 'k-') plt.axvspan(4749, 4759, alpha=0.5, color='blue') plt.axvspan(4778, 4788, alpha=0.5, color='blue') plt.axvspan(4818, 4828, alpha=0.5, color='blue') plt.axvspan(5389, 5399, alpha=0.5, color='blue') plt.axvspan(5532, 5542, alpha=0.5, color='blue') plt.axvspan(6008, 6018, alpha=0.5, color='blue') plt.axvspan(6016, 6026, alpha=0.5, color='blue') plt.axvspan(4335, 4345, alpha=0.5, color='red') plt.axvspan(4856, 4866, alpha=0.5, color='red') plt.axvspan(6558, 6568, alpha=0.5, color='red') plt.savefig(self.outputname + '/' + self.specname + '_obs.png') plt.close() # Get synthetic spectrum, split both obs and synth spectra into red and blue parts synthfluxmask, obsfluxmask, obswvlmask, ivarmask, mask = mask_obs_for_division( self.obswvl, self.obsflux, self.ivar, temp=self.temp, logg=self.logg, fe=self.fe, alpha=self.alpha, dlam=self.dlam, lines=self.lines) if plot: # Plot spliced synthetic spectrum plt.figure() plt.plot(obswvlmask[0], synthfluxmask[0], 'b-') plt.plot(obswvlmask[1], synthfluxmask[1], 'r-') plt.savefig(self.outputname + '/' + self.specname + '_synth.png') plt.close() # Compute continuum-normalized observed spectrum self.obsflux_norm, self.ivar_norm = divide_spec( synthfluxmask, obsfluxmask, obswvlmask, ivarmask, mask, sigmaclip=True, specname=self.specname, outputname=self.outputname) if plot: # Plot continuum-normalized observed spectrum plt.figure() plt.plot(self.obswvl, self.obsflux_norm, 'k-') plt.axvspan(4749, 4759, alpha=0.5, color='blue') plt.axvspan(4778, 4788, alpha=0.5, color='blue') plt.axvspan(4818, 4828, alpha=0.5, color='blue') plt.axvspan(5389, 5399, alpha=0.5, color='blue') plt.axvspan(5532, 5542, alpha=0.5, color='blue') plt.axvspan(6008, 6018, alpha=0.5, color='blue') plt.axvspan(6016, 6026, alpha=0.5, color='blue') plt.axvspan(4335, 4345, alpha=0.5, color='red') plt.axvspan(4856, 4866, alpha=0.5, color='red') plt.axvspan(6558, 6568, alpha=0.5, color='red') plt.ylim((0, 5)) plt.savefig(self.outputname + '/' + self.specname + '_obsnormalized.png') plt.close() np.savetxt( self.outputname + '/' + self.specname + '_obsnormalized.txt', np.asarray((self.obswvl, self.obsflux_norm)).T) if wvlcorr: print('Doing wavelength correction...') try: # Compute standard deviation contdivstd = np.zeros(len(self.ivar_norm)) + np.inf contdivstd[self.ivar_norm > 0] = np.sqrt( np.reciprocal(self.ivar_norm[self.ivar_norm > 0])) # Wavelength correction self.obswvl = fit_wvl(self.obswvl, self.obsflux_norm, contdivstd, self.dlam, self.temp, self.logg, self.fe, self.alpha, self.specname, self.outputname + '/') print('Done with wavelength correction!') except Exception as e: print(repr(e)) print( 'Couldn\'t complete wavelength correction for some reason.' ) # Crop observed spectrum into regions around Mn lines self.obsflux_fit, self.obswvl_fit, self.ivar_fit, self.dlam_fit, self.skip = mask_obs_for_abundance( self.obswvl, self.obsflux_norm, self.ivar_norm, self.dlam, lines=self.lines) # Else, check if we need to open a hi-res file to get the spectrum elif hires is not None: # Output filename if self.globular: self.outputname = '/raid/madlr/glob/' + galaxyname + '/' + 'hires' else: self.outputname = '/raid/madlr/dsph/' + galaxyname + '/' + 'hires' # Open observed spectrum self.specname = hires self.obswvl, self.obsflux, self.dlam = open_obs_file( self.obsfilename, hires=True) # Get measured parameters from obsspecial keyword self.temp = obsspecial[0] self.logg = obsspecial[1] self.fe = obsspecial[2] self.alpha = obsspecial[3] self.fe_err = obsspecial[4] self.zrest = obsspecial[5] self.ivar = np.ones(len(self.obsflux)) # Correct for wavelength self.obswvl = self.obswvl / (1. + self.zrest) print('Redshift: ', self.zrest) # Get synthetic spectrum, split both obs and synth spectra into red and blue parts synthfluxmask, obsfluxmask, obswvlmask, ivarmask, mask = mask_obs_for_division( self.obswvl, self.obsflux, self.ivar, temp=self.temp, logg=self.logg, fe=self.fe, alpha=self.alpha, dlam=self.dlam, lines=self.lines, hires=True) # Compute continuum-normalized observed spectrum self.obsflux_norm, self.ivar_norm = divide_spec( synthfluxmask, obsfluxmask, obswvlmask, ivarmask, mask, specname=self.specname, outputname=self.outputname, hires=True) if smooth is not None: # Crop med-res wavelength range to match hi-res spectrum smooth = smooth[np.where((smooth > self.obswvl[0]) & (smooth < self.obswvl[-1]))] # Interpolate and smooth the synthetic spectrum onto the observed wavelength array self.dlam = np.ones(len(smooth)) * 0.7086 self.obsflux_norm = smooth_gauss_wrapper( self.obswvl, self.obsflux_norm, smooth, self.dlam) self.obswvl = smooth self.ivar_norm = np.ones(len(self.obswvl)) * 1.e4 if plot: # Plot continuum-normalized observed spectrum plt.figure() plt.plot(self.obswvl, self.obsflux_norm, 'k-') plt.savefig(self.outputname + '/' + self.specname + '_obsnormalized.png') plt.close() # Crop observed spectrum into regions around Mn lines self.obsflux_fit, self.obswvl_fit, self.ivar_fit, self.dlam_fit, self.skip = mask_obs_for_abundance( self.obswvl, self.obsflux_norm, self.ivar_norm, self.dlam, lines=self.lines, hires=True) # Else, take both spectrum and observed parameters from obsspecial keyword else: # Output filename self.outputname = '/raid/madlr/test/' + slitmaskname self.obsflux_fit = obsspecial[0] self.obswvl_fit = obsspecial[1] self.ivar_fit = obsspecial[2] self.dlam_fit = obsspecial[3] self.skip = obsspecial[4] self.temp = obsspecial[5] self.logg = obsspecial[6] self.fe = obsspecial[7] self.alpha = obsspecial[8] self.fe_err = obsspecial[9] self.specname = self.slitmaskname # Splice together Mn line regions of observed spectra print('Skip: ', self.skip) self.obsflux_final = np.hstack((self.obsflux_fit[self.skip])) self.obswvl_final = np.hstack((self.obswvl_fit[self.skip])) self.ivar_final = np.hstack((self.ivar_fit[self.skip]))
def run_chisq(filename, paramfilename, galaxyname, slitmaskname, startstar=0, globular=False, lines='new', plots=False, wvlcorr=True, membercheck=None, memberlist=None, velmemberlist=None): """ Measure Mn abundances from a FITS file. Inputs: filename -- file with observed spectra paramfilename -- file with parameters of observed spectra galaxyname -- galaxy name, options: 'scl' slitmaskname -- slitmask name, options: 'scl1' Keywords: startstar -- if 0 (default), start at beginning of file and write new datafile; else, start at #startstar and just append to datafile globular -- if 'False' (default), put into output path of galaxy; else, put into globular cluster path lines -- if 'new' (default), use new revised linelist; else, use original linelist from Judy's code plots -- if 'False' (default), don't plot final fits/resids while doing the fits; else, plot them wvlcorr -- if 'True' (default), do linear wavelength corrections following G. Duggan's code for 900ZD data; else (for 1200B data), don't do corrections membercheck -- do membership check for this object memberlist -- member list (from Evan's Li-rich giants paper) velmemberlist -- member list (from radial velocity check) """ # Output filename if globular: outputname = '/raid/madlr/glob/'+galaxyname+'/'+slitmaskname+'.csv' else: outputname = '/raid/madlr/dsph/'+galaxyname+'/'+slitmaskname+'.csv' # Open new file if startstar<1: with open(outputname, 'w+') as f: f.write('Name\tRA\tDec\tTemp\tlog(g)\t[Fe/H]\terror([Fe/H])\t[alpha/Fe]\t[Mn/H]\terror([Mn/H])\tchisq(reduced)\n') # Prep for member check if membercheck is not None: # Check if stars are in member list from Evan's Li-rich giants paper table = ascii.read(memberlist) memberindex = np.where(table.columns[0] == membercheck) membernames = table.columns[1][memberindex] # Also check if stars are in member list from velocity cut if velmemberlist is not None: oldmembernames = membernames membernames = [] velmembernames = np.genfromtxt(velmemberlist,dtype='str') for i in range(len(oldmembernames)): if oldmembernames[i] in velmembernames: membernames.append(oldmembernames) membernames = np.asarray(membernames) # Get number of stars in file Nstars = open_obs_file(filename) # Get coordinates of stars in file RA, Dec = open_obs_file(filename, coords=True) # Run chi-sq fitting for all stars in file for i in range(startstar, Nstars): try: # Get metallicity of star to use for initial guess print('Getting initial metallicity') temp, logg, fe, alpha, fe_err = open_obs_file(filename, retrievespec=i, specparams=True) # Get dlam (FWHM) of star to use for initial guess specname, obswvl, obsflux, ivar, dlam, zrest = open_obs_file(filename, retrievespec=i) # Check for bad parameter measurement if np.isclose(temp, 4750.) and np.isclose(fe,-1.5) and np.isclose(alpha,0.2): print('Bad parameter measurement! Skipped #'+str(i+1)+'/'+str(Nstars)+' stars') continue # Do membership check if membercheck is not None: if specname not in membernames: print('Not in member list! Skipped '+specname) #'+str(i+1)+'/'+str(Nstars)+' stars') continue # Run optimization code star = chi_sq.obsSpectrum(filename, paramfilename, i, wvlcorr, galaxyname, slitmaskname, globular, lines, RA[i], Dec[i], plot=True) best_mn, error, finalchisq = star.plot_chisq(fe, output=True, plots=plots) except Exception as e: print(repr(e)) print('Skipped star #'+str(i+1)+'/'+str(Nstars)+' stars') continue print('Finished star '+star.specname, '#'+str(i+1)+'/'+str(Nstars)+' stars') #print('test', star.specname, RA[i], Dec[i], star.temp, star.logg, star.fe, star.fe_err, star.alpha, best_mn, error, finalchisq) with open(outputname, 'a') as f: f.write(star.specname+'\t'+str(RA[i])+'\t'+str(Dec[i])+'\t'+str(star.temp)+'\t'+str(star.logg)+'\t'+str(star.fe)+'\t'+str(star.fe_err)+'\t'+str(star.alpha)+'\t'+str(best_mn[0])+'\t'+str(error[0])+'\t'+str(finalchisq)+'\n') return
def plot_fits_postfacto(filename, paramfilename, galaxyname, slitmaskname, startstar=0, globular=False, lines='new', mn_cluster=None): """ Plot fits, residuals, and ivar for stars whose [Mn/H] abundances have already been measured. Inputs: filename -- file with observed spectra paramfilename -- file with parameters of observed spectra galaxyname -- galaxy name, options: 'scl' slitmaskname -- slitmask name, options: 'scl1' Keywords: startstar -- if 0 (default), start at beginning of file and write new datafile; else, start at #startstar and just append to datafile globular -- if 'False' (default), put into output path of galaxy; else, put into globular cluster path lines -- if 'new' (default), use new revised linelist; else, use original linelist from Judy's code mn_cluster -- if not None (default), also plot spectrum with [Mn/H] = mean [Mn/H] of cluster """ # Input filename if globular: file = '/raid/madlr/glob/'+galaxyname+'/'+slitmaskname+'.csv' else: file = '/raid/madlr/dsph/'+galaxyname+'/'+slitmaskname+'.csv' # Output filepath if globular: outputname = '/raid/madlr/glob/'+galaxyname+'/'+slitmaskname else: outputname = '/raid/madlr/dsph/'+galaxyname+'/'+slitmaskname name = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=0, dtype='str') mn = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=8) mnerr = np.genfromtxt(file, delimiter='\t', skip_header=1, usecols=9) # Get number of stars in file with observed spectra Nstars = open_obs_file(filename) # Open file to store reduced chi-sq values chisqfile = outputname+'_chisq.txt' with open(chisqfile, 'w+') as f: print('made it here') f.write('Star'+'\t'+'Line'+'\t'+'redChiSq (best[Mn/H])'+'\t'+'redChiSq (best[Mn/H]+0.15)'+'\t'+'redChiSq (best[Mn/H]-0.15)'+'\n') # Plot spectra for each star for i in range(startstar, Nstars): try: # Check if parameters are measured temp, logg, fe, alpha, fe_err = open_obs_file(filename, retrievespec=i, specparams=True) if np.isclose(1.5,logg) and np.isclose(fe,-1.5) and np.isclose(fe_err, 0.0): print('Bad parameter measurement! Skipped #'+str(i+1)+'/'+str(Nstars)+' stars') continue # Open star star = chi_sq.obsSpectrum(filename, paramfilename, i, False, galaxyname, slitmaskname, globular, lines, plot=True) # Check if star has already had [Mn/H] measured if star.specname in name: # If so, open data file for star if globular: datafile = '/raid/madlr/glob/'+galaxyname+'/'+slitmaskname+'/'+str(star.specname)+'_data.csv' else: datafile = '/raid/madlr/dsph/'+galaxyname+'/'+slitmaskname+'/'+str(star.specname)+'_data.csv' # Get observed and synthetic spectra and inverse variance array obswvl = np.genfromtxt(datafile, delimiter=',', skip_header=2, usecols=0) obsflux = np.genfromtxt(datafile, delimiter=',', skip_header=2, usecols=1) synthflux = np.genfromtxt(datafile, delimiter=',', skip_header=2, usecols=2) #synthfluxup = np.genfromtxt(datafile, delimiter=',', skip_header=2, usecols=3) #synthfluxdown = np.genfromtxt(datafile, delimiter=',', skip_header=2, usecols=4) ivar = np.genfromtxt(datafile, delimiter=',', skip_header=2, usecols=5) idx = np.where(name == star.specname) synthfluxup = star.synthetic(obswvl, mn[idx] + 0.15, full=True) synthfluxdown = star.synthetic(obswvl, mn[idx] - 0.15, full=True) synthflux_nomn = star.synthetic(obswvl, -10.0, full=True) if mn_cluster is not None: synthflux_cluster = [mn_cluster, star.synthetic(obswvl, mn_cluster, full=True)] else: synthflux_cluster=None if mnerr[idx][0] < 1: # Run code to make plots make_plots(lines, star.specname+'_', obswvl, obsflux, synthflux, outputname, ivar=ivar, resids=True, synthfluxup=synthfluxup, synthfluxdown=synthfluxdown, synthflux_nomn=synthflux_nomn, synthflux_cluster=synthflux_cluster, title=None, savechisq=chisqfile) # Write all plotting data to a file hdr = 'Star '+str(star.specname)+'\n'+'obswvl\tobsflux\tsynthflux\tsynthfluxup\tsynthfluxdown\tsynthflux_nomn\n' np.savetxt(outputname+'/'+str(star.specname)+'_finaldata.csv', np.asarray((obswvl,obsflux,synthflux,synthfluxup,synthfluxdown,synthflux_nomn)).T, header=hdr) except Exception as e: print(repr(e)) print('Skipped star #'+str(i+1)+'/'+str(Nstars)+' stars') continue print('Finished star '+star.specname, '#'+str(i+1)+'/'+str(Nstars)+' stars') return