def get_emission_lines(self, vac_or_air='air'): if vac_or_air == 'vac': vacuum = True else: vacuum = False self.emission_lines, self.line_names, self.line_wave = P.emission_lines( self.log_lam_template, self.lam_range_gal, self.FWHM_gal, vacuum=vacuum)
def make_paintbox_model(wave, store, name="test", porder=45, nssps=1, sigma=100): """ Prepare a model with paintbox. """ ssp = pb.CvD18(sigma=sigma, store=store, libpath=context.cvd_dir) twave = ssp.wave limits = ssp.limits if nssps > 1: for i in range(nssps): p0 = pb.Polynomial(twave, 0, pname="w") p0.parnames = [f"w_{i+1}"] s = copy.deepcopy(ssp) s.parnames = ["{}_{}".format(_, i + 1) for _ in s.parnames] if i == 0: pop = p0 * s else: pop += (p0 * s) else: pop = ssp vname = "vsyst_{}".format(name) stars = pb.Resample(wave, pb.LOSVDConv(pop, losvdpars=[vname, "sigma"])) # Adding a polynomial poly = pb.Polynomial(wave, porder, zeroth=True, pname=f"p{name}") # Including emission lines target_fwhm = lambda w: sigma / const.c.to("km/s").value * w * 2.355 gas_templates, gas_names, line_wave = ppxf_util.emission_lines( np.log(twave), [wave[0], wave[-1]], target_fwhm, tie_balmer=True, vacuum=True) gas_templates /= np.max(gas_templates, axis=0) # Normalize line amplitudes gas_names = [_.replace("_", "") for _ in gas_names] # for em in gas_templates.T: # plt.plot(twave, em) # plt.show() if len(gas_names) > 0: emission = pb.NonParametricModel(twave, gas_templates.T, names=gas_names) emkin = pb.Resample( wave, pb.LOSVDConv(emission, losvdpars=[vname, "sigma_gas"])) sed = (stars + emkin) * poly else: sed = stars * poly return sed, limits, gas_names
def ppxf_example_population_gas_sdss(tie_balmer, limit_doublets): ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__)) cube_id = 468 # reading cube_data cube_file = "data/cubes/cube_" + str(cube_id) + ".fits" hdu = fits.open(cube_file) t = hdu[0].data # using our redshift estimate from lmfit cube_result_file = ("results/cube_" + str(cube_id) + "/cube_" + str(cube_id) + "_lmfit.txt") cube_result_file = open(cube_result_file) line_count = 0 for crf_line in cube_result_file: if (line_count == 20): curr_line = crf_line.split() z = float(curr_line[1]) line_count += 1 cube_x_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" + str(int(cube_id)) + "_cbd_x.npy") cube_y_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" + str(int(cube_id)) + "_cbs_y.npy") # Only use the wavelength range in common between galaxy and stellar library. # mask = (cube_x_data > 3540) & (cube_x_data < 7409) flux = cube_y_data[mask] galaxy = flux / np.median( flux) # Normalize spectrum to avoid numerical issues wave = cube_x_data[mask] # The SDSS wavelengths are in vacuum, while the MILES ones are in air. # For a rigorous treatment, the SDSS vacuum wavelengths should be # converted into air wavelengths and the spectra should be resampled. # To avoid resampling, given that the wavelength dependence of the # correction is very weak, I approximate it with a constant factor. # wave *= np.median(util.vac_to_air(wave) / wave) # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0). # A constant noise is not a bad approximation in the fitted wavelength # range and reduces the noise in the fit. # noise = np.full_like(galaxy, 0.01635) # Assume constant noise per pixel here # The velocity step was already chosen by the SDSS pipeline # and we convert it below to km/s # c = 299792.458 # speed of light in km/s velscale = c * np.log(wave[1] / wave[0]) # eq.(8) of Cappellari (2017) FWHM_gal = 2.76 # SDSS has an approximate instrumental resolution FWHM of 2.76A. #------------------- Setup templates ----------------------- pathname = ppxf_dir + '/miles_models/Mun1.30*.fits' miles = lib.miles(pathname, velscale, FWHM_gal) # The stellar templates are reshaped below into a 2-dim array with each # spectrum as a column, however we save the original array dimensions, # which are needed to specify the regularization dimensions # reg_dim = miles.templates.shape[1:] stars_templates = miles.templates.reshape(miles.templates.shape[0], -1) # See the pPXF documentation for the keyword REGUL, regul_err = 0.013 # Desired regularization error # Construct a set of Gaussian emission line templates. # Estimate the wavelength fitted range in the rest frame. # lam_range_gal = np.array([np.min(wave), np.max(wave)]) / (1 + z) gas_templates, gas_names, line_wave = util.emission_lines( miles.log_lam_temp, lam_range_gal, FWHM_gal, tie_balmer=tie_balmer, limit_doublets=limit_doublets) # Combines the stellar and gaseous templates into a single array. # During the PPXF fit they will be assigned a different kinematic # COMPONENT value # templates = np.column_stack([stars_templates, gas_templates]) #----------------------------------------------------------- # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below as described # in PPXF_EXAMPLE_KINEMATICS_SAURON and Sec.2.4 of Cappellari (2017) # c = 299792.458 dv = c * (miles.log_lam_temp[0] - np.log(wave[0]) ) # eq.(8) of Cappellari (2017) vel = c * np.log(1 + z) # eq.(8) of Cappellari (2017) start = [vel, 180.] # (km/s), starting guess for [V, sigma] n_temps = stars_templates.shape[1] n_forbidden = np.sum(["[" in a for a in gas_names]) # forbidden lines contain "[*]" n_balmer = len(gas_names) - n_forbidden # Assign component=0 to the stellar templates, component=1 to the Balmer # gas emission lines templates and component=2 to the forbidden lines. component = [0] * n_temps + [1] * n_balmer + [2] * n_forbidden gas_component = np.array( component) > 0 # gas_component=True for gas templates # Fit (V, sig, h3, h4) moments=4 for the stars # and (V, sig) moments=2 for the two gas kinematic components moments = [4, 2, 2] # Adopt the same starting value for the stars and the two gas components start = [start, start, start] # If the Balmer lines are tied one should allow for gas reddeining. # The gas_reddening can be different from the stellar one, if both are fitted. gas_reddening = 0 if tie_balmer else None # Here the actual fit starts. # # IMPORTANT: Ideally one would like not to use any polynomial in the fit # as the continuum shape contains important information on the population. # Unfortunately this is often not feasible, due to small calibration # uncertainties in the spectral shape. To avoid affecting the line strength of # the spectral features, we exclude additive polynomials (DEGREE=-1) and only use # multiplicative ones (MDEGREE=10). This is only recommended for population, not # for kinematic extraction, where additive polynomials are always recommended. # t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, plot=False, moments=moments, degree=-1, mdegree=10, vsyst=dv, lam=wave, clean=False, regul=1. / regul_err, reg_dim=reg_dim, component=component, gas_component=gas_component, gas_names=gas_names, gas_reddening=gas_reddening) # When the two Delta Chi^2 below are the same, the solution # is the smoothest consistent with the observed spectrum. # print('Desired Delta Chi^2: %.4g' % np.sqrt(2 * galaxy.size)) print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1) * galaxy.size)) print('Elapsed time in PPXF: %.2f s' % (clock() - t)) weights = pp.weights[ ~gas_component] # Exclude weights of the gas templates weights = weights.reshape(reg_dim) / weights.sum() # Normalized miles.mean_age_metal(weights) miles.mass_to_light(weights, band="r") # Plot fit results for stars and gas. plt.clf() plt.subplot(211) pp.plot() # Plot stellar population mass fraction distribution plt.subplot(212) miles.plot(weights) plt.tight_layout() #plt.pause(1) plt.show()
def emission(dirfile, w1, f1, redshift, plateifu, tie_balmer, limit_doublets): ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__)) z = redshift flux = f1 galaxy = flux wave = w1 wave *= np.median(util.vac_to_air(wave) / wave) noise = np.full_like(galaxy, 0.01635) # Assume constant noise per pixel here c = 299792.458 # speed of light in km/s velscale = c * np.log(wave[1] / wave[0]) # eq.(8) of Cappellari (2017) # SDSS has an approximate instrumental resolution FWHM of 2.76A. FWHM_gal = 2.76 # ------------------- Setup templates ----------------------- pathname = ppxf_dir + '/miles_models/Mun1.30*.fits' # The templates are normalized to mean=1 within the FWHM of the V-band. # In this way the weights and mean values are light-weighted quantities miles = lib.miles(pathname, velscale, FWHM_gal) reg_dim = miles.templates.shape[1:] regul_err = 0.013 # Desired regularization error lam_range_gal = np.array([np.min(wave), np.max(wave)]) / (1 + z) gas_templates, gas_names, line_wave = util.emission_lines( miles.log_lam_temp, lam_range_gal, FWHM_gal, tie_balmer=tie_balmer, limit_doublets=limit_doublets) templates = gas_templates c = 299792.458 dv = c * (miles.log_lam_temp[0] - np.log(wave[0]) ) # eq.(8) of Cappellari (2017) vel = c * np.log(1 + z) # eq.(8) of Cappellari (2017) start = [vel, 180.] # (km/s), starting guess for [V, sigma] # n_temps = stars_templates.shape[1] n_forbidden = np.sum(["[" in a for a in gas_names]) # forbidden lines contain "[*]" n_balmer = len(gas_names) - n_forbidden component = [0] * n_balmer + [1] * n_forbidden gas_component = np.array( component) >= 0 # gas_component=True for gas templates moments = [2, 2] start = [start, start] gas_reddening = 0 if tie_balmer else None t = clock() pp = gas.ppxf(dirfile, templates, galaxy, noise, velscale, start, z, plot=True, moments=moments, degree=-1, mdegree=10, vsyst=dv, lam=wave, clean=False, component=component, gas_component=gas_component, gas_names=gas_names, gas_reddening=gas_reddening) pp.plot() return pp.bestfit, pp.lam
def dump_ppxf_results(ppfit, miles, z, outfile): """ Write the stnadard results and the gas_component measurements to a simple ASCII file Parameters ---------- ppfit: ppxf outfile: str Returns ------- """ # Get the lines (air) emission_lines, line_names, line_wave = util.emission_lines( np.array([0.1, 0.2]), [1000., 1e5], 0, limit_doublets=False, vacuum=True) # Construct a simple Table gas_tbl = Table() # Standard pPXF fit results meta = {} meta['EBV'] = ppfit.reddening star_weights = ppfit.weights[~ppfit.gas_component] star_weights = star_weights.reshape(ppfit.reg_dim) age, metals = miles.mean_age_metal(star_weights) meta['AGE'] = age meta['METALS'] = metals # Mass -- Approximate # Mass -- This is a bit approximate as Dwv is a guess for now actualflux = ppfit.bestfit * constants.L_sun.cgs / units.angstrom / ( 4 * np.pi * (cosmo.luminosity_distance(z).to(units.cm))**2 / (1 + z)) # When fitting, the routine thought our data and model spectra had same units... Dwv = 1700. # Ang, width of the band pass scfactor = np.median(ppfit.bestfit * (units.erg / units.s / units.cm**2 / units.angstrom) / actualflux) * Dwv # To get the actual model mass required to fit spectrum, scale by this ratio massmodels = scfactor * miles.total_mass(star_weights) meta['LOGMSTAR'] = np.log10(massmodels.value) gas_tbl.meta = meta gas = ppfit.gas_component comp = ppfit.component[gas] gas_tbl['comp'] = comp gas_tbl['name'] = ppfit.gas_names gas_tbl['flux'] = ppfit.gas_flux gas_tbl['err'] = ppfit.gas_flux_error # Wavelengths waves = [] for name in ppfit.gas_names: idx = np.where(line_names == name)[0][0] waves.append(line_wave[idx]) gas_tbl['wave'] = waves vs = [ppfit.sol[icomp][0] for icomp in comp] sigs = [ppfit.sol[icomp][1] for icomp in comp] gas_tbl['v'] = vs gas_tbl['sig'] = sigs # Write gas_tbl.write(outfile, format='ascii.ecsv', overwrite=True) print("Wrote: {:s}".format(outfile))
def ppxf_single(object_id, z_init, lambda_spec, galaxy_lin, error_lin, cars_model, emsub_specfile=None, plotfile=None, outfile=None, outfile_spec=None, mpoly=None, apoly=None, reflines=None): # Speed of light c = 299792.458 #h_spec = spec_hdu[0].header #Ang_air = h_spec['CRVAL3'] + np.arange(0,h_spec['CDELT3']*(h_spec['NAXIS3']),h_spec['CDELT3']) #s = 10**4/Ang_air #n = 1 + 0.00008336624212083 + 0.02408926869968 / (130.1065924522 - s**2) + 0.000159740894897 / (38.92568793293 - s**2) #lambda_spec = Ang_air*n #wave = h_spec['CRVAL3'] + np.arange(0,h_spec['CDELT3']*(h_spec['NAXIS3']),h_spec['CDELT3']) #lambda_spec = vactoair(wave) #lambda_spec = h_spec['CRVAL3'] + np.arange(0,h_spec['CDELT3']*(h_spec['NAXIS3']),h_spec['CDELT3']) # Crop to finite use = (np.isfinite(galaxy_lin) & (galaxy_lin > 0.0)) # Making a "use" vector use_indices = np.arange(galaxy_lin.shape[0])[use] galaxy_lin = (galaxy_lin[use_indices.min():(use_indices.max() + 1)])[2:-3] error_lin = (error_lin[use_indices.min():(use_indices.max() + 1)])[2:-3] lambda_spec = (lambda_spec[use_indices.min():(use_indices.max() + 1)])[2:-3] lamRange_gal = np.array([np.min(lambda_spec), np.max(lambda_spec)]) # New resolution estimate = 0.9 \AA (sigma), converting from sigma to FWHM FWHM_gal = 2.355 * (np.max(lambda_spec) - np.min(lambda_spec)) / len(lambda_spec) print('FWHM', FWHM_gal) lamRange_gal = lamRange_gal / ( 1 + float(z_init)) # Compute approximate restframe wavelength range FWHM_gal = FWHM_gal / (1 + float(z_init)) # Adjust resolution in Angstrom sigma_gal = FWHM_gal / (2.3548 * 4500.0) * c # at ~4500 \AA # log rebinning for the fits galaxy, logLam_gal, velscale = util.log_rebin(np.around(lamRange_gal, decimals=3), galaxy_lin, flux=True) noise, logLam_noise, velscale = util.log_rebin(np.around(lamRange_gal,decimals=3), error_lin, \ velscale=velscale, flux=True) # correcting for infinite or zero noise noise[np.logical_or((noise == 0.0), np.isnan(noise))] = 1.0 galaxy[np.logical_or((galaxy < 0.0), np.isnan(galaxy))] = 0.0 if galaxy.shape != noise.shape: galaxy = galaxy[:-1] logLam_gal = logLam_gal[:-1] # Define lamRange_temp and logLam_temp #lamRange_temp, logLam_temp = setup_spectral_library_conroy(velscale[0], FWHM_gal) # Construct a set of Gaussian emission line templates. # Estimate the wavelength fitted range in the rest frame. # gas_templates, line_names, line_wave = util.emission_lines( logLam_gal, lamRange_gal, FWHM_gal) goodpixels = np.arange(galaxy.shape[0]) wave = np.exp(logLam_gal) # crop very red end (only affects a very small subsample) # goodpixels = goodpixels[wave <= 5300] # exclude lines at the edges (where things can go very very wrong :)) include_lines = np.where((line_wave > (wave.min() + 10.0)) & (line_wave < (wave.max() - 10.0))) if line_wave[include_lines[0]].shape[0] < line_wave.shape[0]: line_wave = line_wave[include_lines[0]] line_names = line_names[include_lines[0]] gas_templates = gas_templates[:, include_lines[0]] #reg_dim = stars_templates.shape[1:] reg_dim = gas_templates.shape[1:] templates = gas_templates #np.hstack([gas_templates, gas_templates]) dv = 0 #(logLam_temp[0]-logLam_gal[0])*c # km/s vel = 0 #np.log(z_init + 1)*c # Initial estimate of the galaxy velocity in km/s #z = np.exp(vel/c) - 1 # Relation between velocity and redshift in pPXF # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. # t = clock() nNLines = gas_templates.shape[1] component = [0] * nNLines start_gas = [vel, 100.] start = start_gas # adopt the same starting value for both gas (BLs and NLs) and stars moments = [ 2 ] # fit (V,sig,h3,h4) for the stars and (V,sig) for the gas' broad and narrow components fixed = None ## Additive polynomial degree if apoly is None: degree = int( np.ceil((lamRange_gal[1] - lamRange_gal[0]) / (100.0 * (1. + float(z_init))))) else: degree = int(apoly) if mpoly is None: mdegree = 3 else: mdegree = int(mpoly) # Trying: sigmas must be kinematically decoupled # #Trying: velocities must be kinematically decoupled #A_ineq = [[0,0,2,0,-1,0]] #b_ineq = [0] bounds_gas = [[-1000, 1000], [0, 1000]] bounds = bounds_gas pp = ppxf.ppxf(templates, galaxy, noise, velscale, start, fixed=fixed, plot=False, moments=moments, mdegree=mdegree, degree=degree, vsyst=dv, reg_dim=reg_dim, goodpixels=goodpixels, bounds=bounds) #component=component, # redshift_to_newtonian: # return (v-astropy.constants.c.to('km/s').value*redshift)/(1.0+redshift) # "v" from above is actually the converted velocity which is # (np.exp(v_out/c) - 1)*c v_gas = pp.sol[0] ev_gas = pp.error[0] conv_vel_gas = (np.exp(pp.sol[0] / c) - 1) * c vel_gas = (1 + z_init) * (conv_vel_gas - c * z_init) sigma_gas = pp.sol[1] #first # is template, second # is the moment esigma_gas = pp.error[1] #*np.sqrt(pp.chi2) zfit_gas = (z_init + 1) * (1 + pp.sol[0] / c) - 1 zfit_stars = z_init ezfit_gas = (z_init + 1) * pp.error[0] * np.sqrt(pp.chi2) / c if plotfile is not None: # ### All of the rest of this plots and outputs the results of the fit ### Feel free to comment anything out at will # maskedgalaxy = np.copy(galaxy) lowSN = np.where(noise > (0.9 * np.max(noise))) maskedgalaxy[lowSN] = np.nan wave = np.exp(logLam_gal) * (1. + z_init) / (1. + zfit_stars) fig = plt.figure(figsize=(12, 7)) ax1 = fig.add_subplot(211) # plotting smoothed spectrum smoothing_fact = 3 ax1.plot(wave, convolve(maskedgalaxy, Box1DKernel(smoothing_fact)), color='Gray', linewidth=0.5) ax1.plot(wave[goodpixels], convolve(maskedgalaxy[goodpixels], Box1DKernel(smoothing_fact)), 'k', linewidth=1.) label = "Best fit template from high res Conroy SSPs + emission lines at z={0:.3f}".format( zfit_stars) # overplot stellar templates alone ax1.plot(wave, pp.bestfit, 'r', linewidth=1.0, alpha=0.75, label=label) ax1.set_ylabel('Flux') ax1.legend(loc='upper right', fontsize=10) ax1.set_title(object_id) xmin, xmax = ax1.get_xlim() ax2 = fig.add_subplot(413, sharex=ax1, sharey=ax1) # plotting emission lines if included in the fit gas = pp.matrix[:, -nNLines:].dot(pp.weights[-nNLines:]) ax2.plot(wave, gas, 'b', linewidth=2,\ label = '$\sigma_{gas}$'+'={0:.0f}$\pm${1:.0f} km/s'.format(sigma_gas, esigma_gas)+', $V_{gas}$'+'={0:.0f}$\pm${1:.0f} km/s'.format(v_gas, ev_gas)) # overplot emission lines alone cars_model = (cars_model[use_indices.min():(use_indices.max() + 1)])[2:-3] ax2.plot(np.array(lambda_spec)/(1+z_init), cars_model, color='orange', linewidth=1,\ label = 'CARS Model') #(lambda_spec[use_indices.min():(use_indices.max()+1)])[2:-3] stars = pp.bestfit - gas #if (ymax > 3.0*np.median(stars)): ymax = 3.0*np.median(stars) #if (ymin < -0.5): ymin = -0.5 ax2.set_ylabel('Best Fits') ax2.legend(loc='upper left', fontsize=10) # Plotting the residuals ax3 = fig.add_subplot(817, sharex=ax1) ax3.plot(wave[goodpixels], (convolve(maskedgalaxy, Box1DKernel(smoothing_fact)) - pp.bestfit)[goodpixels], 'k', label='Fit Residuals') #ax3.set_yticks([-0.5,0,0.5]) ax3.set_ylabel('Residuals') ax4 = fig.add_subplot(818, sharex=ax1) ax4.plot(wave, noise, 'k', label='Flux Error') ax4.set_ylabel('Noise') ax4.set_xlabel('Rest Frame Wavelength [$\AA$]') #ax4.set_yticks(np.arange(0,0.5,0.1)) '''if reflines is not None: for i,w,label in zip(range(len(reflines)),reflines['wave'],reflines['label']): if ((w > xmin) and (w < xmax)): # ax1.text(w,ymin+(ymax-ymin)*(0.03+0.08*(i % 2)),'$\mathrm{'+label+'}$',fontsize=10,\ # horizontalalignment='center',\ # bbox=dict(boxstyle='round', facecolor='white', alpha=0.5)) print(label.decode("utf-8")) ax1.text(w,ymin+(ymax-ymin)*(0.03+0.08*(i % 2)),'$\mathrm{'+label.decode("utf-8")+'}$',fontsize=10,\ horizontalalignment='center',\ bbox=dict(boxstyle='round', facecolor='white', alpha=0.5)) ax1.plot([w,w],[ymin,ymax],':k',alpha=0.5)''' fig.subplots_adjust(hspace=0.05) plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False) #print('Saving figure to {0}'.format(plotfile)) ax1.set_xlim([6450, 6750]) ax2.set_xlim([6450, 6750]) #ymin, ymax = ax1.get_ylim([]) plt.savefig(plotfile, dpi=150) plt.close() return v_gas, vel_gas, sigma_gas, pp.chi2 # print('# id z_stars ez_stars sigma_stars esigma_stars z_gas ez_gas sigma_gas esigma_gas SN_median SN_rf_4000 SN_obs_8030 chi2dof') print('{0:d} {1:.6f} {2:.6f} {3:.1f} {4:.1f} {5:.6f} {6:.6f} {7:.1f} {8:.1f} {9:.1f} {10:.1f} {11:.1f} {12:.2f}\n'.format(\ object_id,zfit_stars,ezfit_stars, sigma_stars,esigma_stars,\ zfit_gas, ezfit_gas, sigma_gas, esigma_gas, SN_median, SN_rf_4000, SN_obs_8030, pp.chi2)) ## This prints the fit parameters to an open file object called outfile if outfile is not None: outfile.write('{0:d} {1:d} {2:.6f} {3:.6f} {4:.1f} {5:.1f} {6:.6f} {7:.6f} {8:.1f} {9:.1f} {10:.1f} {11:.1f} {12:.1f} {13:.2f}\n'.format(\ object_id, row2D, zfit_stars, ezfit_stars, sigma_stars,esigma_stars,\ zfit_gas, ezfit_gas, sigma_gas, esigma_gas, SN_median, SN_rf_4000, SN_obs_8030, pp.chi2)) ## This outputs the spectrum and best-fit model to an open file object called outfile_spec if outfile_spec is not None: outfile_spec.write( '# l f ef f_stars f_gas f_model_tot used_in_fit add_poly mult_poly\n' ) for i in np.arange(wave.shape[0]): isgood = 0 if goodpixels[goodpixels == i].shape[0] == 1: isgood = 1 outfile_spec.write( '{0:0.4f} {1:0.4f} {2:0.4f} {3:0.4f} {4:0.4f} {5:0.4f} {6} {7:0.4f} {8:0.4f}\n' .format(wave[i], galaxy[i], noise[i], stars[i], gas[i], pp.bestfit[i], isgood, add_polynomial[i], mult_polynomial[i])) # ## This outputs the best-fit emission-subtracted spectrum to a fits file # ## but I've commented it out since you are unlikely to need this! # if emsub_specfile is not None: # wave = np.exp(logLam_gal)*(1.+z_init) # if include_gas: # emsub = galaxy - gas # else: # emsub = galaxy # col1 = fits.Column(name='wavelength', format='E', array=wave) # col2 = fits.Column(name='flux',format='E',array=galaxy) # col3 = fits.Column(name='error',format='E',array=noise) # col4 = fits.Column(name='flux_emsub',format='E',array=emsub) # cols = fits.ColDefs([col1,col2,col3,col4]) # tbhdu = fits.BinTableHDU.from_columns(cols) # # delete old file if it exists # if os.path.isfile(emsub_specfile): os.remove(emsub_specfile) # tbhdu.writeto(emsub_specfile) # return zfit_stars, ezfit_stars, sigma_stars, esigma_stars, zfit_gas, ezfit_gas, \ # sigma_gas, esigma_gas, SN_median, SN_rf_4000, SN_obs_8030, pp.chi2 return zfit_stars, sigma_stars, sigma_blr, wave, pp.bestfit