def ppxf_population(specerr, templates=None, velscale=None, start = None, goodpixels = None, plot=False, moments=4, degree=-1, vsyst=None, clean=False, mdegree=10, regul=None): ''' Function that calls ppxf, designed to be called from multiprocessing (i.e. only require 1 argument). Everything else is taken care of outside of this. ''' galaxy = specerr[0] noise = specerr[1] # Only run ppxf if I have a non-nan's spectrum. Even one nan's breaks ppxf ... sigh. if not(np.any(np.isnan(galaxy))): pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=False, moments=moments, degree=degree, vsyst=vsyst, clean=clean, mdegree=mdegree, regul=regul, quiet=True) # In a perfect world, I would return pp. But that is a very massive variable. # 1 row = 300 spaxels = 5GB => 1 MUSE cube = 1.5TB !!! # Instead, just store what I need/trust. The best fit, the galaxy spectra (for # sanity checks) and the sol array (with the kinematics information, that I # sort-of trust. return [pp.bestfit, pp.galaxy, pp.sol] else: return None
def run_ppxf(wavelength_log, templates, galaxy, noise, velscale, start, goodpixels, dv): pp = ppxf.ppxf(templates, galaxy, noise, velscale, start, bias=0, moments=2, goodpixels=goodpixels, vsyst=dv, quiet=True, degree=-1, mdegree=6, oversample=20) rv = round(pp.sol[0], 1) sigma = round(pp.sol[1], 1) chi2 = pp.chi2 error_rv = pp.error[0] * np.sqrt(chi2) error_sigma = pp.error[1] * np.sqrt(chi2) bestfit = pp.bestfit residuals = bestfit - galaxy zp1 = 1 + rv / c output_wavelengths = wavelength_log / zp1 return rv, sigma, error_rv, error_sigma, chi2, bestfit, residuals, zp1, output_wavelengths
def mc_errors(self, nsim=200): """ Calculate the errors using MC simulations""" errs = np.zeros((nsim, len(self.error))) for i in range(nsim): y = self.bestfit + np.random.normal(0, self.noise, len( self.galaxy)) noise = np.ones_like(self.galaxy) * self.noise sim = ppxf(self.bestfit_unbroad, y, noise, velscale, [0, self.sol[1]], goodpixels=self.goodpixels, plot=False, moments=4, degree=-1, mdegree=-1, vsyst=self.vsyst, lam=self.lam, quiet=True, bias=0.) errs[i] = sim.sol median = np.ma.median(errs, axis=0) error = 1.4826 * np.ma.median(np.ma.abs(errs - median), axis=0) # Here I am using always the maximum error between the simulated # and the values given by pPXF. self.error = np.maximum(error, self.error) return
def run_ppxf(spectra, velscale): """ Run pPXF in a list of spectra""" print "Loading templates" templates, logLam2, delta = load_templates(velscale) for i, spec in enumerate(spectra): print "pPXF run of spectrum {0} ({1} of {2})".format(spec, i+1, len(spectra)) print "Preparing files..." # Read a galaxy spectrum and define the wavelength range hdu = pf.open(spec) spec_lin = hdu[0].data h1 = hdu[0].header lamRange1 = h1['CRVAL1'] + np.array([0.,h1['CDELT1']*(h1['NAXIS1']-1)]) # Convolve our spectra to match MILES resolution FWHM_dif = np.sqrt(FWHM_tem**2 - FWHM_spec**2) sigma = FWHM_dif/2.355/delta # Sigma difference in pixels spec_lin = ndimage.gaussian_filter1d(spec_lin,sigma) # Rebin to logarithm scale galaxy, logLam1, velscale = util.log_rebin(lamRange1, spec_lin, velscale=velscale) noise = 0. * galaxy + 0.1 dv = (logLam2[0]-logLam1[0])*c start, goodPixels = read_setup_file(spec, logLam1) print "First interaction of pPXF.." pp0 = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=False, moments=4, degree=6, mdegree=4, vsyst=dv) print "Calculating realistic noise for input..." rms0 = galaxy[goodPixels] - pp0.bestfit[goodPixels] noise0 = 1.4826 * np.median(np.abs(rms0 - np.median(rms0))) noise0 = 0. * galaxy + noise0 print "Second run of pPXF..." pp = ppxf(templates, galaxy, noise0, velscale, pp0.sol, goodpixels=goodPixels, plot=False, moments=4, degree=6, mdegree=4, vsyst=dv, lam=lamRange1) print "Finished! Now saving results..." with open(spec.replace(".fits", ".pkl"), "w") as f: pickle.dump(pp, f) return
def run_ppxf(wavelength_log, templates, galaxy, noise, velscale, start, goodpixels, dv): pp = ppxf.ppxf(templates, galaxy, noise, velscale, start, bias = 0, moments=2, goodpixels = goodpixels, vsyst=dv, quiet=True, degree = -1, mdegree = 6, oversample = 20) rv = round(pp.sol[0],1) sigma = round(pp.sol[1],1) chi2 = pp.chi2 error_rv = pp.error[0]*np.sqrt(chi2) error_sigma = pp.error[1]*np.sqrt(chi2) bestfit = pp.bestfit residuals = bestfit - galaxy zp1 = 1 + rv/c output_wavelengths = wavelength_log / zp1 return rv, sigma, error_rv, error_sigma, chi2, bestfit, residuals, zp1, output_wavelengths
def mc_errors(self, nsim=10): """ Calculate the errors using MC simulations""" errs = np.zeros((nsim, len(self.error))) templates, logLam2, delta = load_templates(velscale) for i in range(nsim): y = self.galaxy + np.random.normal(0, self.noise, len(self.bestfit)) noise = np.ones_like(self.galaxy) * self.noise sim = ppxf(templates, y, noise, velscale, self.sol, goodpixels=self.goodpixels, plot=False, moments=4, degree=self.degree, mdegree=self.mdegree, vsyst=self.vsyst, lam=self.lam, quiet=True) errs[i] = sim.sol self.mc_err = np.ma.std(errs, axis=0) self.erroor = np.maximum(self.error, self.mc_err) return
def ppxf_population(specerr, templates=None, velscale=None, start=None, goodpixels=None, plot=False, moments=4, degree=-1, vsyst=None, clean=False, mdegree=10, regul=None): ''' Function that calls ppxf, designed to be called from multiprocessing (i.e. only require 1 argument). Everything else is taken care of outside of this. ''' galaxy = specerr[0] noise = specerr[1] # Only run ppxf if I have a non-nan's spectrum. Even one nan's breaks ppxf ... sigh. if not (np.any(np.isnan(galaxy))): pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=False, moments=moments, degree=degree, vsyst=vsyst, clean=clean, mdegree=mdegree, regul=regul, quiet=True) # In a perfect world, I would return pp. But that is a very massive variable. # 1 row = 300 spaxels = 5GB => 1 MUSE cube = 1.5TB !!! # Instead, just store what I need/trust. The best fit, the galaxy spectra (for # sanity checks) and the sol array (with the kinematics information, that I # sort-of trust. return [pp.bestfit, pp.galaxy, pp.sol] else: return None
def call_ppxf(datum, templates, vel_scale, delta_v, good_pixels, quiet, moments=2, degree=7, interp_func='nearest'): if moments == 2: priors = (datum.rv_prior, datum.sigma_prior) elif moments == 4: priors = (datum.rv_prior, datum.sigma_prior, datum.h3_prior, datum.h4_prior) output = ppxf.ppxf(templates, datum.log_fluxes, datum.log_sigmas, vel_scale, moments=moments, mdegree=degree, degree=-1, clean=True, vsyst=delta_v, start=priors, quiet=quiet, goodpixels=good_pixels) datum.rv = output.sol[0] datum.sigma = output.sol[1] if moments == 4: datum.h3 = output.sol[2] datum.h4 = output.sol[3] datum.log_fit_fluxes = output.bestfit datum.fit_wavelengths, datum.fit_fluxes = interp.logtolinear( datum.log_wavelengths, datum.log_fit_fluxes, function=interp_func, ratio=True) datum.rest_wavelengths = datum.fit_wavelengths / ( 1 + datum.rv * 1e3 / constants.c) return datum
def check_ppxf(spec, velscale): """ Checking if velocity os star is zero""" templates, logLam2, delta, miles= stellar_templates(velscale) FWHM_dif = np.sqrt(FWHM_tem**2 - FWHM_spec**2) sigma = FWHM_dif/2.355/delta # Sigma difference in pixels star = pf.getdata(spec) star1 = ndimage.gaussian_filter1d(star, sigma) w = wavelength_array(spec) lamRange= np.array([w[0], w[-1]]) galaxy, logLam1, velscale = util.log_rebin(lamRange, star1, \ velscale=velscale) sn = snr(star1) noise = np.ones_like(galaxy) / sn dv = (logLam2[0]-logLam1[0])*c pp = ppxf(templates, galaxy, noise, velscale, [0,50], plot=True, moments=2, degree=5, mdegree=-1, vsyst=dv) plt.show() return
def mc_errors(self, nsim=200): """ Calculate the errors using MC simulations""" errs = np.zeros((nsim, len(self.error))) for i in range(nsim): y = self.bestfit + np.random.normal(0, self.noise, len(self.galaxy)) noise = np.ones_like(self.galaxy) * self.noise sim = ppxf(self.bestfit_unbroad, y, noise, velscale, [0, self.sol[1]], goodpixels=self.goodpixels, plot=False, moments=4, degree=-1, mdegree=-1, vsyst=self.vsyst, lam=self.lam, quiet=True, bias=0.) errs[i] = sim.sol median = np.ma.median(errs, axis=0) error = 1.4826 * np.ma.median(np.ma.abs(errs - median), axis=0) # Here I am using always the maximum error between the simulated # and the values given by pPXF. self.error = np.maximum(error, self.error) return
def ppxf_gas_kinematics_parallel_loop(bin_sci_j, ppxf_bestfit_j, gal_hdr, templates, velscale, sigma, lamRange1, logLam2, start, plot, moments, regul_err, reg_dim, component, bias, quiet): gal_data = fits.getdata(bin_sci_j, 0) gal_data_new = ndimage.gaussian_filter1d(gal_data, sigma) galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_data_new, velscale=velscale) noise = galaxy * 0 + 1 # Assume constant noise per pixel here # 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_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, plot=plot, moments=moments, degree=-1, mdegree=10, vsyst=dv, clean=False, regul=1. / regul_err, reg_dim=reg_dim, component=component, bias=bias, quiet=quiet) print ('pPXF sol of bin {}: {}'.format(j, pp.sol)) print ('pPXF error of bin {}: {}'.format(j, pp.error)) hdu_best = fits.PrimaryHDU() hdu_best.data = pp.bestfit hdu_best.header['CD1_1'] = gal_hdr['CD1_1'] hdu_best.header['CDELT1'] = gal_hdr['CD1_1'] hdu_best.header['CRPIX1'] = gal_hdr['CRPIX1'] hdu_best.header['CRVAL1'] = gal_hdr['CRVAL1'] hdu_best.header['NAXIS1'] = pp.bestfit.size hdu_best.header['CTYPE1'] = 'LINEAR' # corresponds to sampling of values above hdu_best.header['DC-FLAG'] = '1' # 0 = linear, 1= log-linear sampling hdu_best.writeto(ppxf_bestfit_j, clobber=True) return pp
def ppxf_simulation_example(): dir = 'spectra/' file = dir + 'Rbi1.30z+0.00t12.59.fits' hdu = pyfits.open(file) ssp = hdu[0].data h = hdu[0].header lamRange = h['CRVAL1'] + np.array([0., h['CDELT1'] * (h['NAXIS1'] - 1)]) star, logLam, velscale = util.log_rebin(lamRange, ssp) # The finite sampling of the observed spectrum is modeled in detail: # the galaxy spectrum is obtained by oversampling the actual observed spectrum # to a high resolution. This represent the true spectrum, which is later resampled # to lower resolution to simulate the observations on the CCD. Similarly, the # convolution with a well-sampled LOSVD is done on the high-resolution spectrum, # and later resampled to the observed resolution before fitting with PPXF. factor = 10 # Oversampling integer factor for an accurate convolution starNew = ndimage.interpolation.zoom( star, factor, order=1) # This is the underlying spectrum, known at high resolution star = rebin( starNew, factor ) # Make sure that the observed spectrum is the integral over the pixels vel = 0.3 # velocity in *pixels* [=V(km/s)/velScale] h3 = 0.1 # Adopted G-H parameters of the LOSVD h4 = -0.1 sn = 60. # Adopted S/N of the Monte Carlo simulation m = 300 # Number of realizations of the simulation sigmaV = np.linspace( 0.8, 4, m) # Range of sigma in *pixels* [=sigma(km/s)/velScale] result = np.zeros((m, 4)) # This will store the results t = clock() np.random.seed(123) # for reproducible results for j in range(m): sigma = sigmaV[j] dx = int( abs(vel) + 4.0 * sigma) # Sample the Gaussian and GH at least to vel+4*sigma x = np.linspace( -dx, dx, 2 * dx * factor + 1) # Evaluate the Gaussian using steps of 1/factor pixels. w = (x - vel) / sigma w2 = w**2 gauss = np.exp(-0.5 * w2) / (np.sqrt(2. * np.pi) * sigma * factor ) # Normalized total(gauss)=1 h3poly = w * (2. * w2 - 3.) / np.sqrt(3.) # H3(y) h4poly = (w2 * (4. * w2 - 12.) + 3.) / np.sqrt(24.) # H4(y) losvd = gauss * (1. + h3 * h3poly + h4 * h4poly) galaxy = signal.fftconvolve( starNew, losvd, mode="same") # Convolve the oversampled spectrum galaxy = rebin( galaxy, factor) # Integrate spectrum into original spectral pixels noise = galaxy / sn # 1sigma error spectrum galaxy = np.random.normal(galaxy, noise) # Add noise to the galaxy spectrum start = np.array([ vel + np.random.random(), sigma * np.random.uniform(0.85, 1.15) ]) * velscale # Convert to km/s pp = ppxf(star, galaxy, noise, velscale, start, goodpixels=np.arange(dx, galaxy.size - dx), plot=False, moments=4, bias=0.5) result[j, :] = pp.sol print('Calculation time: %.2f s' % (clock() - t)) plt.clf() plt.subplot(221) plt.plot(sigmaV * velscale, result[:, 0] - vel * velscale, '+k') plt.plot(sigmaV * velscale, sigmaV * velscale * 0, '-r') plt.ylim(-40, 40) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$V - V_{in}\ (km\ s^{-1}$)') plt.subplot(222) plt.plot(sigmaV * velscale, result[:, 1] - sigmaV * velscale, '+k') plt.plot(sigmaV * velscale, sigmaV * velscale * 0, '-r') plt.ylim(-40, 40) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$\sigma - \sigma_{in}\ (km\ s^{-1}$)') plt.subplot(223) plt.plot(sigmaV * velscale, result[:, 2], '+k') plt.plot(sigmaV * velscale, sigmaV * velscale * 0 + h3, '-r') plt.ylim(-0.2 + h3, 0.2 + h3) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$h_3$') plt.subplot(224) plt.plot(sigmaV * velscale, result[:, 3], '+k') plt.plot(sigmaV * velscale, sigmaV * velscale * 0 + h4, '-r') plt.ylim(-0.2 + h4, 0.2 + h4) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$h_4$') plt.tight_layout() plt.pause(0.01)
def ppxf_kinematics(bin_sci, ppxf_file, ppxf_bestfit, template_fits, template_resolution, lam_range=[4173., 5404.], vel_init=1500., sig_init=100., bias=0., plot=False, quiet=True, badPixels=[], clean=False): """ Follow the pPXF usage example by Michile Cappellari INPUT: DIR_SCI_COMB (comb_fits_sci_{S/N}/bin_sci_{S/N}.fits), TEMPLATE_* (spectra/Mun1.30z*.fits) OUTPUT: PPXF_FILE (ppxf_output_sn30.txt), OUT_BESTFIT_FITS (ppxf_fits/ppxf_bestfit_sn30.fits) """ if os.path.exists(ppxf_file): print('File {} already exists'.format(ppxf_file)) return # Read a galaxy spectrum and define the wavelength range assert len(glob.glob(bin_sci.format('*'))) > 0, 'Binned spectra {} not found'.format(glob.glob(bin_sci.format('*'))) # hdu = fits.open(in_file[0]) # gal_lin = hdu[0].data # h1 = hdu[0].header with fits.open(bin_sci.format(0)) as gal_hdu: gal_lin = gal_hdu[0].data gal_hdr = gal_hdu[0].header # lamRange1 = h1['CRVAL1'] + np.array([0.,h1['CDELT1']*(h1['NAXIS1']-1)]) # FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # lamRange1 is now variable lam_range (because I was lazy and didnt put headers into the binned spectra) FWHM_gal = 2.3 # GMOS IFU has an instrumental resolution FWHM of 2.3 A # If the galaxy is at a significant redshift (z > 0.03), one would need to apply a large velocity shift in PPXF to # match the template to the galaxy spectrum.This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! The solution consists of bringing the galaxy # spectrum roughly to the rest-frame wavelength, before calling PPXF. In practice there is no need to modify the # spectrum before the usual LOG_REBIN, given that a red shift corresponds to a linear shift of the log-rebinned # spectrum. One just needs to compute the wavelength range in the rest-frame and adjust the instrumental resolution # of the galaxy observations. This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_lin) # galaxy = galaxy/np.median(galaxy) # Normalize spectrum to avoid numerical issues # noise = galaxy*0 + 0.0049 # Assume constant noise per pixel here galaxy, logLam1, velscale = util.log_rebin(lam_range, gal_lin) galaxy = galaxy / np.median(galaxy) # Normalize spectrum to avoid numerical issues # Read the list of filenames from the Single Stellar Population library by Vazdekis (1999, ApJ, 513, 224). A subset # of the library is included for this example with permission. See http://purl.org/cappellari/software # for suggestions of more up-to-date stellar libraries. # vazdekis = glob.glob(dir + 'Rbi1.30z*.fits') # vazdekis.sort() # FWHM_tem = 1.8 # Vazdekis spectra have a resolution FWHM of 1.8A. template_spectra = glob.glob(template_fits) assert len(template_spectra) > 0, 'Template spectra not found: {}'.format(template_fits) FWHM_tem = template_resolution # Extract the wavelength range and logarithmically rebin one spectrum to the same velocity scale of the SAURON # galaxy spectrum, to determine the size needed for the array which will contain the template spectra. # hdu = fits.open(vazdekis[0]) # ssp = hdu[0].data # h2 = hdu[0].header # lamRange2 = h2['CRVAL1'] + np.array([0.,h2['CDELT1']*(h2['NAXIS1']-1)]) # sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) # templates = np.empty((sspNew.size,len(vazdekis))) with fits.open(template_spectra[0]) as temp_hdu: ssp = temp_hdu[0].data h2 = temp_hdu[0].header lamRange2 = h2['CRVAL1'] + np.array([0., h2['CDELT1'] * (h2['NAXIS1'] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size, len(template_spectra))) # Convolve the whole Vazdekis library of spectral templates with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin and store each template as a column in the array TEMPLATES # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the instrumental spectral profiles are well approximated # by Gaussians. # FROM GWENS IDL SCRIPT ---------------------------------------------------------- # Example: BC03 spectra have a resolution of 3A at 5100A, this corresponds to sigma ~ 3.0/5100*3e5/2.35 = 75 km/s. # The GMOS IFU has an instrumental resolution of 2.3 A, this corresponds to sigma ~ 2.3/5100*3e5/2.35 = 56.8 km/s. # The quadratic difference is sigma = sqrt(56.8^2 - 75^2) = undefined # (the above reasoning can be applied if the shape of the instrumental spectral profiles can be well approximated # by a Gaussian). # For the lower resolution models, we must degrade the DATA to fit the models. # Thus: The quadratic difference is sigma = sqrt(75^2 - 56.8^2) = 49.0 km/s # --------------------------------------------------------------------------------- # FWHM_dif = np.sqrt(FWHM_gal ** 2 - template_resolution ** 2) FWHM_dif = np.sqrt(FWHM_tem ** 2 - FWHM_gal ** 2) sigma = FWHM_dif / 2.355 / h2['CDELT1'] # SIGMA DIFFERENCE IN PIXELS, 1.078435697220085 # Logarithmically rebin the whole Mun library of spectra, and store each template as a column in the array TEMPLATES # for j in range(len(vazdekis)): # hdu = fits.open(vazdekis[j]) # ssp = hdu[0].data # ssp = ndimage.gaussian_filter1d(ssp, sigma) # sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) # templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates for j in range(len(template_spectra)): with fits.open(template_spectra[j]) as temp_hdu_j: ssp_j = temp_hdu_j[0].data ssp_j = ndimage.gaussian_filter1d(ssp_j, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp_j, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes 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 (see above). vel_list = [] sig_list = [] dV_list = [] dsigma_list = [] h3_list = [] h4_list = [] dh3_list = [] dh4_list = [] for j in range(len(glob.glob(bin_sci.format('*')))): b_gal = fits.getdata(bin_sci.format(j), 0) b_gal = ndimage.gaussian_filter1d(b_gal, sigma) galaxy, logLam1, velscale = util.log_rebin(lam_range, b_gal, velscale=velscale) noise = galaxy * 0 + 1 # Assume constant noise per pixel here c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s # vel = 1500. # Initial estimate of the galaxy velocity in km/s z = np.exp(vel_init / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) if len(badPixels) > 0: indices = [] for idx, pix in enumerate(goodPixels): if pix in badPixels: indices.append(idx) goodPixels = np.delete(goodPixels, indices) # 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. start = [vel_init, sig_init] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=plot, moments=4, degree=4, vsyst=dv, bias=bias, quiet=False, clean=clean) if not quiet: print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) # If the galaxy is at significant redshift z and the wavelength has been de-redshifted with the three lines # "z = 1.23..." near the beginning of this procedure, the best-fitting redshift is now given by the following # commented line (equation 2 of Cappellari et al. 2009, ApJ, 704, L34; # http://adsabs.harvard.edu/abs/2009ApJ...704L..34C) # print, 'Best-fitting redshift z:', (z + 1)*(1 + sol[0]/c) - 1 # Gwen obtains the velocity and sigma information from the SOL parameter # moments = 4 so sol = [vel, sig, h3, h4] vel_list.append(pp.sol[0]) sig_list.append(pp.sol[1]) dV_list.append((pp.error * np.sqrt(pp.chi2))[0]) dsigma_list.append((pp.error * np.sqrt(pp.chi2))[1]) h3_list.append(pp.sol[2]) h4_list.append(pp.sol[3]) dh3_list.append((pp.error * np.sqrt(pp.chi2))[2]) dh4_list.append((pp.error * np.sqrt(pp.chi2))[3]) hdu_best = fits.PrimaryHDU() hdu_best.data = pp.bestfit hdu_best.header['CD1_1'] = gal_hdr['CD1_1'] hdu_best.header['CDELT1'] = gal_hdr['CD1_1'] hdu_best.header['CRPIX1'] = gal_hdr['CRPIX1'] hdu_best.header['CRVAL1'] = gal_hdr['CRVAL1'] hdu_best.header['NAXIS1'] = pp.bestfit.size hdu_best.header['CTYPE1'] = 'LINEAR' # corresponds to sampling of values above hdu_best.header['DC-FLAG'] = '1' # 0 = linear, 1= log-linear sampling hdu_best.writeto(ppxf_bestfit.format(j), clobber=True) print('Elapsed time in PPXF: %.2f s' % (clock() - t)) np.savetxt(ppxf_file, np.column_stack([vel_list, sig_list, h3_list, h4_list, dV_list, dsigma_list, dh3_list, dh4_list]), fmt=b'%10.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f', header='velocity sigma h3 h4 dV dsigma dh3 dh4') return vel_list
def ppxf_population_gas_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS_DR8.fits' hdu = pyfits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t['wavelength'] > 3540) & (t['wavelength'] < 7409) flux = t['flux'][mask] galaxy = flux/np.median(flux) # Normalize spectrum to avoid numerical issues wave = t['wavelength'][mask] # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0). # A constant error is not a bad approximation in the fitted wavelength # range and reduces the noise in the fit. # noise = galaxy*0 + 0.01528 # 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 = np.log(wave[1]/wave[0])*c FWHM_gal = 2.76 # SDSS has an instrumental resolution FWHM of 2.76A. #------------------- Setup templates ----------------------- stars_templates, lamRange_temp, logLam_temp = \ setup_spectral_library(velscale, FWHM_gal) # The stellar templates are reshaped 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 = stars_templates.shape[1:] stars_templates = stars_templates.reshape(stars_templates.shape[0], -1) # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # stars_templates /= np.median(stars_templates) # Normalizes stellar templates by a scalar regul_err = 0.004 # Desired regularization error # Construct a set of Gaussian emission line templates. # Estimate the wavelength fitted range in the rest frame. # lamRange_gal = np.array([np.min(wave), np.max(wave)])/(1 + z) gas_templates, line_names, line_wave = \ util.emission_lines(logLam_temp, lamRange_gal, FWHM_gal) # 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.hstack([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_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = (np.log(lamRange_temp[0])-np.log(wave[0]))*c # km/s vel = c*z # Initial estimate of the galaxy velocity in km/s # Here the actual fit starts. The best fit is plotted on the screen. # # 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. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] t = clock() # Assign component=0 to the stellar templates and # component=1 to the gas emission lines templates. # One can easily assign different kinematic components to different gas species # e.g. component=1 for the Balmer series, component=2 for the [OIII] doublet, ...) # Input a negative MOMENTS value to keep fixed the LOSVD of a component. # nTemps = stars_templates.shape[1] nLines = gas_templates.shape[1] component = [0]*nTemps + [1]*nLines moments = [4, 2] # fit (V,sig,h3,h4) for the stars and (V,sig) for the gas start = [start, start] # adopt the same starting value for both gas and stars pp = ppxf(templates, galaxy, noise, velscale, start, plot=False, moments=moments, degree=-1, mdegree=10, vsyst=dv, clean=False, regul=1./regul_err, reg_dim=reg_dim, component=component) # Plot fit results for stars and gas plt.clf() plt.subplot(211) plt.plot(wave, pp.galaxy, 'k') plt.plot(wave, pp.bestfit, 'b', linewidth=2) plt.xlabel("Observed Wavelength ($\AA$)") plt.ylabel("Relative Flux") plt.ylim([-0.1,1.3]) plt.xlim([np.min(wave), np.max(wave)]) plt.plot(wave, pp.galaxy-pp.bestfit, 'd', ms=4, color='LimeGreen', mec='LimeGreen') # fit residuals plt.axhline(y=-0, linestyle='--', color='k', linewidth=2) stars = pp.matrix[:,:nTemps].dot(pp.weights[:nTemps]) plt.plot(wave, stars, 'r', linewidth=2) # overplot stellar templates alone gas = pp.matrix[:,-nLines:].dot(pp.weights[-nLines:]) plt.plot(wave, gas+0.15, 'b', linewidth=2) # overplot emission lines alone # 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)) w = np.where(np.array(component) == 1)[0] # Extract weights of gas emissions print('++++++++++++++++++++++++++++++') print('Gas V=%.4g and sigma=%.2g km/s' % (pp.sol[1][0], pp.sol[1][1])) print('Emission lines peak intensity:') for name, weight, line in zip(line_names, pp.weights[w], pp.matrix[:,w].T): print('%12s: %.3g' % (name, weight*np.max(line))) print('------------------------------') # Plot stellar population mass distribution plt.subplot(212) weights = pp.weights[:np.prod(reg_dim)].reshape(reg_dim)/pp.weights.sum() plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', origin='upper', extent=(np.log10(1.0), np.log10(17.7828), -1.9, 0.45)) plt.colorbar() plt.title("Mass Fraction") plt.xlabel("log$_{10}$ Age (Gyr)") plt.ylabel("[M/H]") plt.tight_layout() plt.show()
def ppxf_nifs_kinematics(): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure # Read a galaxy spectrum and define the wavelength range # file = file_dir + '/spectra/pgc12557_combined.fits' # '/spectra/NGC4550_SAURON.fits' # my current file location hdu = fits.open(file) gal_lin = hdu[1].data # gal_lin = hdu[0].data # h1 = hdu[0].header h1 = hdu[ 1].header # I need to use 1st extension header (0=general, 1=science, 2=variance, 3=data quality flags) ''' print(h1) BITPIX = -32 / array data type NAXIS = 3 / number of array dimensions NAXIS1 = 71 NAXIS2 = 69 NAXIS3 = 2040 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups EXTNAME = 'SCI ' / Extension name EXTVER = 1 / Extension version INHERIT = F / Inherits global header ORIGIN = 'NOAO-IRAF FITS Image Kernel July 2003' / FITS file originator OBJECT = 'PGC12557' / Name of the object observed DISPAXIS= 3 / Dispersion axis PIXSCALE= 0.05 / Pixel scale in arcsec/pixel CTYPE1 = 'LINEAR ' / coordinate type for the dispersion axis CTYPE2 = 'LINEAR ' / coordinate type for the spatial axis DC-FLAG = 0 / CD1_1 = 0.05 / CD2_2 = 0.05 / DCLOG1 = 'Transform' WCSDIM = 3 / CRVAL1 = 2.987 CRPIX1 = 68. CRPIX2 = 2. WAT1_001= 'wtype=linear axtype=xi' / WAT2_001= 'wtype=linear axtype=eta' / CTYPE3 = 'LINEAR ' / WAT3_001= 'wtype=linear axtype=wave' / CRVAL3 = 19971.869140625 / CD3_3 = 2.1321439743 / AIRMASS = 1.374 CRPIX3 = 1. LTM1_1 = 1. LTM2_2 = 1. LTM3_3 = 1. WAT0_001= 'system=world' END ''' ''' # NOTE: Copied from util.log_rebin(): lamRange: two elements vector containing the central wavelength of the first and last pixels in the spectrum, which is assumed to have constant wavelength scale! E.g. from the values in the standard FITS keywords: LAMRANGE = CRVAL1 + [0, CDELT1*(NAXIS1 - 1)]. It must be LAMRANGE[0] < LAMRANGE[1]. ''' # lamRange1 = h1['CRVAL1'] + np.array([0., h1['CDELT1']*(h1['NAXIS1'] - 1)]) # original # lamRange1 = h1['CRVAL3'] + np.array([0., h1['CD3_3']*(h1['CRPIX3'])]) # [ 19971.86914062 19974.0012846 ] # if I use CRPIX3, probably don't want CRPIX3 - 1 because CRPIX3 = 1., and need lamRange1[0] < lamRange1[1] lamRange1 = h1['CRVAL3'] + np.array( [0., h1['CD3_3'] * (h1['NAXIS3'] - 1)]) # [ 19971.86914062 24319.31070422] print(lamRange1, 'l1') # print(gal_lin[0][20]) all 0s # print((gal_lin[300][35])) NOT all 0s! # print(len(lamRange1)) # len(lamRange1) = 2, # len(gal_lin) = 2040, len(gal_lin[0]) = 69, len(gal_lin[0][1]) = 71 # 2040 --> NAXIS 3, 69 --> NAXIS2, 71 --> NAXIS1 # HMM: want gal_lin to be spectrum itself, which should just be 1d array # There's a len 2040 spectrum at each gal_lin[:,x,y] # CUT SPECTRUM TO WAVELENGTH RANGE 2.26 - 2.42 # SO: gal_lin is an array len(2040) starting at lamRange1[0] and finishing at lamRange1[1], with each pixel in # between separated by h1['CD3_3']. So find [22600 - lamRange1[0]] / CD3_3, and that should give the number of # pixels between pixel 1 and the pixel corresponding roughly to 2.26 microns. Then find [24200 - lamRange1[1]] / # CD3_3, which should give the number of pixels between pixel 1 and the pixel corresponding to 2.42 microns. start = 22600. stop = 24200. cut1 = (start - lamRange1[0]) / h1['CD3_3'] cut2 = (stop - lamRange1[0]) / h1['CD3_3'] # print(cut1, cut2, 'cuts') # 1232.62354281, 1983.04191009 gal_lin = gal_lin[int(cut1):int(cut2)] # [1233:1983] # start1 = h1['CRVAL3'] + h1['CD3_3'] * int(cut1) # stop1 = h1['CRVAL3'] + h1['CD3_3'] * int(cut2) # print(start1, stop1, 'me') lamRange1 = [ h1['CRVAL3'] + h1['CD3_3'] * int(cut1), h1['CRVAL3'] + h1['CD3_3'] * int(cut2) ] print(lamRange1, 'l1, cut') # len(gal_lin) is now NOT 2040 but 1983 - 1233 = 750 FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # BUCKET: do I need this? If so what for? # If the galaxy is at significant redshift, one should bring the galaxy # spectrum roughly to the rest-frame wavelength, before calling pPXF # (See Sec2.4 of Cappellari 2017). In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # There's a len 2040 spectrum at each gal_lin[:,x,y] x = 33 y = 35 galaxy, logLam1, velscale = util.log_rebin( lamRange1, gal_lin[:, x, y]) # no input velscale --> function returns print(len(galaxy), 'len gal') # 750 now because of cut to gal_lin! galaxy = galaxy / np.median( galaxy) # Normalize spectrum to avoid numerical issues # print(galaxy) # BUCKET: constant noise/pix good assumption for me? If so what value do I use? TRY our noise! Maybe trust more?! # basically bootstrap the noise! # Do one fit with flat noise, then save the best fit spectrum and the residuals. # Then, iterate ~200 times. For these iterations, set bias = 0.0. Each iteration, for each pixel, use the spectrum # value as the center of a gaussian and use the residuals at that pixel value as the width of the gaussian. Draw # from the resultant distribution to make the new noise. For each iteration, save the output V, sigma, h3, h4, and # print each spectrum so that we can see it evolving (it should look more like a real spectrum, rather than smooth # curve without lines) noise = np.full_like(galaxy, 0.0047) # Assume constant noise per pixel here # MEANTIME: why is output velocity close to systemic insted of close to 0, if I'm dealing with redshift already? # SET bias = 0 # # print(noise.shape) # 751, # print(galaxy.shape) # 751, # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob( file_dir + '/veltemps/*.fits') # '/miles_models/Mun1.30Z*.fits') # my new # BUCKET: what are FWHM of spectra in the veltemps library?? FWHM_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # BUCKET: what spectral sampling compared to galaxy do we want for templates?? # velscale_ratio = 2 # 1.23 # 2 # adopts 2x higher spectral sampling for templates than for galaxy # PROBLEM!! If velscale_ratio is not integer, we get issue later because we slice a list with velscale_ratio # so need to change velscale? But it's set by util.log_rebin originally! Only need this if oversampling the # templates, which I'm not doing # Extract the wavelength range and logarithmically rebin one spectrum # to a velocity scale 2x smaller than the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # print(vazdekis[0], 'template name') hdu = fits.open( vazdekis[0] ) # do for just one template to determine size needed for array containing all templates ssp = hdu[ 1].data # was hdu[0], but that's generic header rather than science header h2 = hdu[1].header # was hdu[0] ''' for line in h2: print(line, h2[line]) XTENSION IMAGE BITPIX -32 NAXIS 1 NAXIS1 1721 PCOUNT 0 GCOUNT 1 EXTNAME SCI EXTVER 1 INHERIT False ORIGIN NOAO-IRAF FITS Image Kernel July 2003 OBJECT BD-01 3097 DATE 2015-02-15T13:32:06 IRAF-TLM 2015-02-15T13:32:30 NFPAD 2015-02-14T14:40:41 GEM-TLM 2015-02-14T14:40:41 DISPAXIS 1 PIXSCALE 0.043 NSCUTSEC [1:2040,1145:1213] NSCUTSPC 13 FIXPIX Feb 14 8:44 Bad pixel file is tmpdq20234_650 CTYPE1 LINEAR CD1_1 2.13 CTYPE2 LINEAR CD2_2 0.04570113 DCLOG1 Transform DC-FLAG 0 WCSDIM 3 CRVAL1 20628.29 CRPIX1 1.0 CRPIX2 -28.0 WAXMAP01 1 0 0 29 0 0 WAT1_001 wtype=linear axtype=wave WAT2_001 wtype=linear axtype=eta CTYPE3 LINEAR WAT3_001 wtype=linear axtype=xi CRVAL3 1.751 CD3_3 0.103 LTV2 -29.0 LTM1_1 1.0 LTM2_2 1.0 LTM3_3 1.0 WAT0_001 system=image EXPTIME 25.0 IMCMB001 xatfbrsnN20070508S0187.fits[SCI] IMCMB002 xatfbrsnN20070508S0190.fits[SCI] IMCMB003 xatfbrsnN20070508S0191.fits[SCI] IMCMB004 xatfbrsnN20070508S0194.fits[SCI] NCOMBINE 4 GAINORIG 1.0 RONORIG 0.0 GAIN 2.666667 RDNOISE 0.0 ''' # lamRange2 = h2['CRVAL1'] + np.array([0., h2['CDELT1']*(h2['NAXIS1'] - 1)]) # original lamRange2 = h2['CRVAL1'] + np.array([0., h2['CD1_1'] * (h2['NAXIS1'] - 1) ]) # BUCKET want NAXIS - 1? # lamRange2 = h2['CRVAL1'] + np.array([0., h2['CD1_1']*(h2['CRPIX1'])]) # or use CRPIX1?? print(lamRange2, 'l2') # [ 20628.29 24291.89] # print((lamRange2[1] - lamRange2[0])/h2['CD1_1'], 'num of steps in lam2') # 1720. # print(len(ssp), 'len ssp') # 1721 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(len(sspNew), 'len sspnew') # 622 hmmmm NEED THIS TO BE >= (len(galaxy)=750) # FIXED, now 1791 templates = np.empty((sspNew.size, len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) # sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels sigma = FWHM_dif / 2.355 / h2['CD1_1'] # Sigma difference in pixels for j, file in enumerate( vazdekis ): # now for each template file; so why do the thing above with just 1?? hdu = fits.open(file) # ssp = hdu[0].data ssp = hdu[1].data ssp = ndimage.gaussian_filter1d(ssp, sigma) # ndimage.gaussian_filter takes input array (ssp) and filters it. Sigma = standard deviation of Gaussian kernel # used to filter the array # note: discrete convolution is defined (for any 2 arrays a, v): (a*v)[n] == sum m(-inf to inf) of a[m]*v[n-m] # where a is the original curve and v is the gaussian # print(len(ssp)) # 1721 --> currently by default being resampled to be 116, want it to be resampled to be 71 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(velscale_temp, 'vt') # 78.82967746 # print(velscale, 'v') # 78.82967746 # print(len(sspNew)) # need this to be >= len(galaxy) # now 1791 templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates # print(len(templates[0])) # len(templates)=29, len(templates[0] = 19) # 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 (see above). # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s ''' if velscale_ratio > 1: dv = (np.mean(logLam2[:velscale_ratio]) - logLam1[0])*c # km/s else: dv = (logLam2[0] - logLam1[0])*c # km/s ''' z = 0.016561 # z = 0.0015 # Initial redshift estimate of the galaxy goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # 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. # vel = c * np.log(1 + z) # eq.(8) of Cappellari (2017) start = [vel, 200.] # [vel, 200.] # (km/s), starting guess for [V, sigma] t = clock() # print(galaxy.shape[0]) # = len(galaxy) = 750 # print(goodPixels) # print(max(noise[:, x, y]), min(noise[:, x, y]), np.median(noise[:, x, y]), np.mean(noise[:, x, y])) # 0.00106575 -2.77079e-05 4.62628e-05 5.89877e-05 pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv, velscale_ratio=1) # velscale_ratio=velscale_ratio) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) print('Elapsed time in pPXF: %.2f s' % (clock() - t)) # If the galaxy is at significant redshift z and the wavelength has been # de-redshifted with the three lines "z = 1.23..." near the beginning of # this procedure, the best-fitting redshift is now given by the following # commented line (equation 2 of Cappellari et al. 2009, ApJ, 704, L34; # http://adsabs.harvard.edu/abs/2009ApJ...704L..34C) # # print('Best-fitting redshift z:', (z + 1)*(1 + pp.sol[0]/c) - 1) return pp.bestfit, pp.galaxy, pp.sol # output_spectrum, output_noise # for use in noise_in
def __init__(self, galaxy='ngc3557', slit_h=4.5, slit_w=2, slit_pa=30, method='Rampazzo_aperture', r1=0, r2=None, debug=False): print galaxy if 'Rampazzo' in method: from Rampazzo import rampazzo # Default is nuclear region: 0<r<R_e/16 if r2 is None: r2 = get_R_e(galaxy)/16 data = rampazzo(galaxy, method='aperture', slit_h=slit_h, r1=r1, r2=r2, debug=debug) if 'gradient' in method: if '2' in method: data.method = 'gradient2' else: data.method = 'gradient' elif method == 'Ogando': from Ogando import ogando data = ogando(galaxy, debug=debug, slit_h=slit_h, slit_w=slit_w, slit_pa=slit_pa) elif method == 'Miles': from Miles import miles data = miles(galaxy) gal_spec, gal_noise = data.get_spec() gal_spec, lam, cut = apply_range(gal_spec, window=201, repeats=3, lam=data.lam, set_range=np.array([4200,10000]), return_cuts=True) gal_noise = gal_noise[cut] lamRange = np.array([lam[0],lam[-1]])/(1+data.z) ## ----------================= Templates ====================--------- FWHM_gal = 2.5 # VIMOS documentation (and fits header) FWHM_gal = FWHM_gal/(1+data.z) # Adjust resolution in Angstrom stellar_templates = get_stellar_templates(galaxy, FWHM_gal) velscale = stellar_templates.velscale e_templates = get_emission_templates(gas, lamRange, stellar_templates.logLam_template, FWHM_gal) if gas: templates = np.column_stack((stellar_templates.templates, e_templates.templates)) else: templates = stellar_templates.templates component = [0]*len(stellar_templates.templatesToUse) + e_templates.component templatesToUse = np.append(stellar_templates.templatesToUse, e_templates.templatesToUse) element = ['stellar'] + e_templates.element start = [[data.vel, data.sig]] * (max(component) + 1) moments = [stellar_moments] + [gas_moments] * max(component) ## ----------============== Final calibrations ==============--------- ## smooth spectrum to fit with templates resolution if FWHM_gal < stellar_templates.FWHM_tem: sigma = stellar_templates.FWHM_dif/2.355/data.f[0].header['CDELT3'] gal_spec = ndimage.gaussian_filter1d(gal_spec, sigma) gal_noise = np.sqrt(ndimage.gaussian_filter1d(gal_noise**2, sigma)) ## rebin spectrum logarthmically gal_spec_log, logLam_bin, _ = util.log_rebin(lamRange, gal_spec, velscale=velscale) gal_noise_log, logLam_bin, _ = util.log_rebin(lamRange, gal_noise**2, velscale=velscale) gal_noise_log = np.sqrt(gal_noise_log) gal_noise_log = gal_noise_log + 0.0000000000001 dv = (stellar_templates.logLam_template[0]-logLam_bin[0])*c # km/s # Find the pixels to ignore to avoid being distracted by gas emission #; lines or atmospheric absorbsion line. goodPixels = determine_goodpixels(logLam_bin,stellar_templates.lamRange_template, data.vel, data.z, gas=gas!=0) lambdaq = np.exp(logLam_bin) ## ----------=================== pPXF =======================--------- pp = ppxf(templates, gal_spec_log, gal_noise_log, velscale, start, goodpixels=goodPixels, moments=moments, degree=-1, vsyst=dv, component=component, lam=lambdaq, plot=not quiet, quiet=quiet, mdegree=10) self.pp = pp # Only use stellar templates (Remove emission lines) stellar_spec = gal_spec_log - \ pp.matrix[:, -e_templates.ntemp:].dot(pp.weights[-e_templates.ntemp:]) conv_spec = pp.matrix[:, :-e_templates.ntemp].dot(pp.weights[:-e_templates.ntemp]) # Generate the unconvolved spectra ('0 km/s' resolution) unc_lam = stellar_templates.wav unc_spec = stellar_templates.lin_templates.dot(pp.weights[:stellar_templates.ntemp]) unc_spec *= np.polynomial.legendre.legval(np.linspace(-1,1, len(unc_spec)), np.append(1, pp.mpolyweights)) ## ----------============== Absorption Line =================--------- lines = ['G4300', 'Fe4383', 'Ca4455', 'Fe4531', 'H_beta', 'Fe5015', 'Mg_b'] self.result = {} self.uncert = {} for line in lines: ab, ab_uncert = absorption(line, lambdaq, stellar_spec, noise=gal_noise_log, unc_lam=unc_lam, unc_spec=unc_spec, conv_spec=conv_spec)#, #lick=True) if method == 'Ogando': # Aperture correction ab_file = '%s/Documents/useful_files/ab_linelist.dat' % (cc.home_dir) i1, i2, b1, b2, r1, r2, units = np.genfromtxt(ab_file, unpack=True, usecols=(1,2,3,4,5,6,7), skip_header=2, skip_footer=2) ls = np.genfromtxt(ab_file, unpack=True, dtype=str, usecols=(8), skip_header=2, skip_footer=2) l = np.where(ls == line)[0][0] index = [i1[l], i2[l]] # Convert to mag ab = -2.5 * np.log(1 - ab/(index[1]-index[0])) # Aperture Correction: beta values taken from paper beta = {'H_beta':0.002, 'Fe5015':-0.012, 'Mg_b':-0.031} # If line is not observed by Ogando if line not in beta.keys(): self.result[line] = np.nan self.uncert[line] = np.nan continue H = 70.0 # value used by Bolonga group. r_ab = np.degrees(1.19/1000 * H/(c * data.z)) * 60 * 60 # 1.19 kpc -> arcsec # Correction is def in eq (9) with r_ab and r_norm def in eq (1) - not # 100% sure if I've got them correct. ab = ab - beta[line] * np.log(1.025 * np.sqrt(slit_w*r_ab/np.pi)/slit_h) # Back to Angstroms ab = (index[1]-index[0]) * (1 - np.exp(ab/-2.5)) elif 'Rampazzo' in method: self.r = data.r self.result[line] = ab self.uncert[line] = ab_uncert if debug: for line in lines: print '%s: %.3f +/- %.3f' % (line, self.result[line], self.uncert[line])
def ppxfit(ncompfit, brot, bfract, mom): velscale = 110. file = 'NGC0528-V500.rscube.fits' hdu = pyfits.open(file) gal_lin = hdu[0].data h1 = hdu[0].header medfl = np.loadtxt("medgalpy.txt") x = medfl[:, 0] y = medfl[:, 1] sig = medfl[:, 2] noise = medfl[:, 3] bins = np.loadtxt("voronoi_2d_binning_output.txt", skiprows=1) x = bins[:, 0] y = bins[:, 1] binnum = bins[:, 2] binco = np.loadtxt("bins.txt") xbin = binco[:, 0] ybin = binco[:, 1] file = 'galaxybinspy.fits' # spectra arranged horizontally hdu = pyfits.open(file) gal_bin = hdu[0].data gs = gal_bin.shape nbins = gs[0] xcut = 0.0 ycut = 0.0 delta = h1['CDELT3'] lamRange1 = h1['CRVAL3'] + np.array( [xcut * delta, delta * ((h1['NAXIS3'] - 1) - ycut)]) FWHM_gal = 6.0 # CALIFA has an instrumental resolution FWHM of 6A. galaxyz, logLam1, velscale = util.log_rebin(lamRange1, gal_bin[0, :], velscale=velscale) galaxy = np.empty((galaxyz.size, nbins)) noise = np.empty((galaxyz.size, nbins)) for j in range(nbins): galaxy[:, j], logLam1, velscale = util.log_rebin(lamRange1, gal_bin[j, :], velscale=velscale) galaxy[:, j] = galaxy[:, j] / np.median( galaxy[:, j]) # Normalize spectrum to avoid numerical issues noise[:, j] = galaxy[:, j] * 0 + 0.0049 # Assume constant noise per pixel here #dir='/home/ppxmt3/astro/MILES/' dir = 'galspec/' miles = glob.glob(dir + 'Mun*.fits') miles.sort() FWHM_tem = 2.5 # Miles spectra have a resolution FWHM of 1.8A. age = np.empty(len(miles)) met = np.empty(len(miles)) #age=np.chararray(len(miles),itemsize=7) #met=np.chararray(len(miles),itemsize=5) for j in range(len(miles)): ast = miles[j][22:29] mst = miles[j][17:21] pm = miles[j][16:17] if pm == 'p': pmn = '+' elif pm == 'm': pmn = '-' mstpm = (pmn, mst) #met[j,:]=miles[j][16:19] age[j] = float(ast) met[j] = float("".join(mstpm)) #age2,inda=np.unique(age,return_inverse=True) #met2,ind=np.unique(met,return_inverse=True) #c=1 #for i in range(len(age2)/2): #indout=np.where(age==age2[c])[0] ##print(indout) #miles=np.delete(miles,indout) #age=np.delete(age,indout) #c=c+2 # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the CALIFA galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. hdu = pyfits.open(miles[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array( [0., h2['CDELT1'] * (h2['NAXIS1'] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) # Convolve the whole miles library of spectral templates # with the quadratic difference between the CALIFA and the # miles instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels miles --> CALIFA # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif / 2.355 / h2['CDELT1'] # Sigma difference in pixels #========================================================================================================================== #One component fit - saves veloctiy values in 'NGC528_onecompkin.txt' to be used as initial estimates for two component fit if ncompfit == 1: templates = np.empty((sspNew.size, len(miles))) for j in range(len(miles)): hdu = pyfits.open(miles[j]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 4750. # Initial estimate of the galaxy velocity in km/s z = np.exp( vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) start = np.zeros(2) output = np.zeros((nbins, 5)) output[:, 0] = xbin[:] output[:, 1] = ybin[:] start[:] = [vel, 3. * velscale] # (km/s), starting guess for [V,sigma] for j in range(nbins): print('On ', j + 1, ' out of ', nbins) print(start) pp = ppxf(templates, galaxy[:, j], noise[:, j], velscale, start, goodpixels=goodPixels, degree=4, vsyst=dv, plot=True, moments=mom) kinem = np.loadtxt("ppxfout.txt") if mom == 2: output[j, 2] = kinem[0] #vel output[j, 3] = kinem[1] #sigma output[j, 4] = kinem[2] #chisq if mom == 4: output[j, 2] = kinem[0] #vel output[j, 3] = kinem[1] #sigma output[j, 4] = kinem[4] #chisq np.savetxt('NGC528_onecompkinm2nch.txt', output, fmt="%10.3g") #========================================================================================================================= #Two component fit elif ncompfit == 2: # To determine flux fraction of bulge. Set bfract to 0 to disable # 'bulgediskblock.fits' is created by running GALFIT to get a galfit.01 file of best fit parameters, then using # '>galfit -o3 galfit.01' to get cube of galaxy image, bulge image and disk image if bfract == 1: file = 'bulgediskblock.fits' hdu = pyfits.open(file) galb = hdu[1].data bulge = hdu[2].data disk = hdu[3].data # Bin bulge and disk images into same binning as datacube nbins = xbin.shape[0] avbulge = np.zeros(nbins) avdisk = np.zeros(nbins) avtot = np.zeros(nbins) binflux = np.zeros(nbins) x = x.astype(int) y = y.astype(int) for j in range(nbins): b = np.where(binnum == j)[0] valbin = b.size if valbin == 1: avbulge[j] = bulge[y[b], x[b]] avdisk[j] = disk[y[b], x[b]] avtot[j] = galb[x[b], y[b]] else: avbulge[j] = np.median(bulge[y[b], x[b]]) avdisk[j] = np.median(disk[y[b], x[b]]) avtot[j] = np.median(galb[x[b], y[b]]) bulge_fraction = avbulge / (avbulge + avdisk) hdu = pyfits.PrimaryHDU(bulge_fraction) hdu.writeto('bulge_fraction.fits', clobber=True) #==================================================================================== templates = np.empty((sspNew.size, 2 * len(miles))) ssparr = np.empty((ssp.size, len(miles))) for j in range(len(miles)): hdu = pyfits.open(miles[j]) ssparr[:, j] = hdu[0].data ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates for j in range(len(miles), 2 * len(miles)): hdu = pyfits.open(miles[j - len(miles)]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates component = np.zeros((2 * len(miles)), dtype=np.int) component[0:len(miles)] = 0 component[len(miles):] = 1 c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 4750. # Initial estimate of the galaxy velocity in km/s z = np.exp( vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) kin = np.loadtxt('NGC528_onecompkinnch.txt') xbin = kin[:, 0] ybin = kin[:, 1] vpxf = kin[:, 2] spxf = kin[:, 3] occh = kin[:, 4] velbulge = vel sigdisk = 50. file = 'bulge_fraction.fits' # Read out bulge fraction for each bin hdu = pyfits.open(file) bulge_fraction = hdu[0].data bvel = np.zeros(nbins) bsig = np.zeros(nbins) bh3 = np.zeros(nbins) bh4 = np.zeros(nbins) dvel = np.zeros(nbins) dsig = np.zeros(nbins) dh3 = np.zeros(nbins) dh4 = np.zeros(nbins) bwt = np.zeros(nbins) dwt = np.zeros(nbins) output = np.zeros((nbins, 10 + (2 * (mom - 2)))) popoutput = np.zeros((nbins, 6)) output[:, 0] = xbin[:] output[:, 1] = ybin[:] popoutput[:, 0] = xbin[:] popoutput[:, 1] = ybin[:] bmet = np.zeros(nbins) bage = np.zeros(nbins) dage = np.zeros(nbins) dmet = np.zeros(nbins) count = 0 for j in range(2, nbins - 1): print('Bin number:', j + 1, 'out of', nbins) print('Bulge fraction:', bulge_fraction[j]) if spxf[j] > 350: spxf[j] = 350. if abs(vpxf[j] - 4750.) > 300: vpxf[j] = 4750. #start = np.array([[vpxf[j], spxf[j]],[vpxf[j],sigdisk]]) # (km/s), starting guess for [V,sigma] start = np.array([[velbulge, spxf[j]], [vpxf[j], sigdisk] ]) # (km/s), starting guess for [V,sigma] print('Starting velocity estimates:', start[0, 0], start[0, 1], start[1, 0], start[1, 1]) print('Xbin:', xbin[j], 'Ybin:', ybin[j]) t = clock() pp = ppxf(templates, galaxy[:, j], noise[:, j], velscale, start, bulge_fraction=bulge_fraction[j], goodpixels=goodPixels, moments=[mom, mom], degree=4, vsyst=dv, component=component, brot=1, plot=True) #brot=0 for nonrotating, brot=1 for rotating #Kinematics kinem = np.loadtxt("ppxfout.txt") wts = np.loadtxt("ppxfoutwt.txt") if mom == 2: output[j, 2] = kinem[0, 0] #bvel output[j, 3] = kinem[0, 1] #bsig output[j, 4] = kinem[1, 0] #dvel output[j, 5] = kinem[1, 1] #dsig output[j, 6] = wts[0] #bulge weight output[j, 7] = wts[1] #disk weight output[j, 8] = wts[2] #chisqn output[j, 9] = wts[3] #chisq if mom == 4: output[j, 2] = kinem[0, 0] #bvel output[j, 3] = kinem[0, 1] #bsig output[j, 4] = kinem[0, 2] #bh3 output[j, 5] = kinem[0, 3] #bh4 output[j, 6] = kinem[1, 0] #dvel output[j, 7] = kinem[1, 1] #dsig output[j, 8] = kinem[1, 2] #dh3 output[j, 9] = kinem[1, 3] #dh4 output[j, 10] = wts[0] #bulge weight output[j, 11] = wts[1] #disk weight output[j, 12] = wts[2] #chisqn output[j, 13] = wts[3] #chisq print(wts[0], wts[1]) print('Chisq difference from one comp fit (pos = improved)', occh[j] - wts[2]) if occh[j] > wts[2]: count = count + 1 bwt[j] = wts[0] dwt[j] = wts[1] #Populations #wtsb=np.loadtxt("ppxfoutwtsb.txt") #wtsd=np.loadtxt("ppxfoutwtsd.txt") #shwb=wtsb.shape #shwd=wtsd.shape #if len(shwb) > 1: #bulgewt=np.array(wtsb[0,:]) #bulgewt=bulgewt/bulgewt.sum() #bulgewtin=np.array(wtsb[1,:],dtype=int) #else: #bulgewt=1. #bulgewtin=np.int(wtsb[1]) #if len(shwd) > 1: #diskwt=np.array(wtsd[0,:]) #diskwt=diskwt/diskwt.sum() #diskwtin=np.array(wtsd[1,:],dtype=int) #else: #diskwt=1. #diskwtin=np.int(wtsd[1]) #bage[j]=np.dot(bulgewt,age[bulgewtin]) #bmet[j]=np.dot(bulgewt,met[bulgewtin]) #dage[j]=np.dot(diskwt,age[diskwtin]) #dmet[j]=np.dot(diskwt,met[diskwtin]) #popoutput[j,2]=bage[j] #popoutput[j,3]=bmet[j] #popoutput[j,4]=dage[j] #popoutput[j,5]=dmet[j] #Plots #ssparr=templates #print(gal_bin.shape) #gal_bin=galaxy #print(gal_bin.shape) #bulgespec=ssparr[:,bulgewtin].dot(bulgewt) #diskspec=ssparr[:,diskwtin].dot(diskwt) #diskspec=diskspec/np.median(diskspec) #bulgespec=bulgespec/np.median(bulgespec) #plt.xlabel("Wavelength") #plt.ylabel("Counts") #plt.plot(5*(gal_bin[goodPixels,j]/np.median(gal_bin[goodPixels,j])), 'k') #plt.plot(3*(bulgespec[goodPixels]), 'r') #plt.plot(2*(diskspec[goodPixels]), 'b') #plt.plot(5*(bulgespec[goodPixels]+diskspec[goodPixels])/np.median(bulgespec[goodPixels]+diskspec[goodPixels]), 'g') #plt.savefig('outfit') np.savetxt('NGC528conskinnchcheckbrot.txt', output, fmt="%10.3g")
def ppxf_example_sky_and_symmetric_losvd(): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure # Solar metallicity, Age=12.59 Gyr hdu = fits.open(file_dir + '/miles_models/Mun1.30Zp0.00T12.5893.fits') ssp = hdu[0].data h = hdu[0].header lamRange = h['CRVAL1'] + np.array([0., h['CDELT1'] * (h['NAXIS1'] - 1)]) velscale = 70. # km/s star, logLam, velscale = util.log_rebin(lamRange, ssp, velscale=velscale) star /= np.mean(star) # Adopted input parameters ================================================= vel = 200. / velscale # Velocity of 1st spectrum in pixels (2nd has -vel) sigma = 300. / velscale # Dispersion of both spectra in pixels h3 = 0.1 # h3 of 1st spectrum (2nd has -h3) h4 = 0.1 sn = 40. moments = 4 deg = 4 vshift = 10 # Adopted systemic velocity in pixels vsyst = vshift * velscale # Adopted systemic velocity in km/s # Generate input Sky ======================================================= # For illustration, the sky is modelled as two Gaussian emission lines n = star.size x = np.arange(n) sky1 = np.exp(-0.5 * (x - 1000)**2 / 100) sky2 = np.exp(-0.5 * (x - 2000)**2 / 100) # Generate input LOSVD ===================================================== dx = int(abs(vel) + 5 * sigma) v = np.linspace(-dx, dx, 2 * dx + 1) w = (v - vel) / sigma w2 = w**2 gauss = np.exp(-0.5 * w2) gauss /= np.sum(gauss) h3poly = w * (2 * w2 - 3) / np.sqrt(3) h4poly = (w2 * (4 * w2 - 12) + 3) / np.sqrt(24) losvd = gauss * (1 + h3 * h3poly + h4 * h4poly) # Generate first synthetic spectrum ======================================== # The template is convolved with the LOSVD x = np.linspace(-1, 1, n) galaxy1 = signal.fftconvolve(star, losvd, mode="same") galaxy1 = np.roll(galaxy1, vshift) # Mimic nonzero systemic velocity galaxy1 *= legendre.legval( x, np.append(1, np.random.uniform(-0.1, 0.1, deg - 1))) # Multiplicative polynomials galaxy1 += legendre.legval(x, np.random.uniform(-0.1, 0.1, deg)) # Additive polynomials galaxy1 += sky1 + 2 * sky2 # Add two sky lines galaxy1 = np.random.normal(galaxy1, 1 / sn) # Add noise # Generate symmetric synthetic spectrum ==================================== # The same template is convolved with a reversed LOSVD # and different polynomials and sky lines are included galaxy2 = signal.fftconvolve(star, np.flip(losvd, 0), mode="same") galaxy2 = np.roll(galaxy2, vshift) # Mimic nonzero systemic velocity galaxy2 *= legendre.legval( x, np.append(1, np.random.uniform(-0.1, 0.1, deg - 1))) # Multiplicative polynomials galaxy2 += legendre.legval(x, np.random.uniform(-0.1, 0.1, deg)) # Additive polynomials galaxy2 += 2 * sky1 + sky2 # Add two sky lines galaxy2 = np.random.normal(galaxy2, 1 / sn) # Add noise # Load spectral templates ================================================== vazdekis = glob.glob(file_dir + '/miles_models/Mun1.30Z*.fits') templates = np.empty((n, len(vazdekis))) for j, file in enumerate(vazdekis): hdu = fits.open(file) ssp = hdu[0].data sspNew, logLam2, velscale = util.log_rebin(lamRange, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalize templates # Do the fit =============================================================== # Input both galaxy spectra simultaneously to pPXF galaxy = np.column_stack([galaxy1, galaxy2]) # Use two sky templates for each galaxy spectrum sky = np.column_stack([sky1, sky2]) # Randomized starting guess vel0 = vel + np.random.uniform(-1, 1) sigma0 = sigma * np.random.uniform(0.8, 1.2) start = np.array([vel0, sigma0]) * velscale # Convert to km/s goodpixels = np.arange(50, n - 50) print( "\nThe input values are: Vel=%0.0f, sigma=%0.0f, h3=%0.1f, h4=%0.1f\n" % (vel * velscale, sigma * velscale, h3, h4)) t = clock() pp = ppxf(templates, galaxy, np.full_like(galaxy, 1 / sn), velscale, start, goodpixels=goodpixels, plot=1, moments=moments, vsyst=vsyst, mdegree=deg, degree=deg, sky=sky) print('Elapsed time in pPXF: %.2f s' % (clock() - t)) plt.pause(1)
norm=np.median(templates) templates /= norm full /= norm dv = c*np.log(lamRange_temp[0]/wave[0]) # km/s goodpixels = util.determine_goodpixels(log_wave, lamRange_temp, z) start = [c*np.log(1 + z), 0.01]#3*velscale] fixed = [True, True]#, False, False] fixed = [False, False] print start pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=False, moments=2, degree=-1, vsyst=dv, clean=False, regul=1, mdegree=0, reddening=0.1,lam=wave, fixed=fixed ) # noise *= np.sqrt(pp.chi2) # pp = ppxf(templates, galaxy, noise, velscale, start, # goodpixels=goodpixels, plot=False, moments=2, degree=-1, # vsyst=dv, clean=False, regul=0., # mdegree=0, reddening=None,lam=wave # ) pp.grid={'Z':metal_grid,'logAge':logAge_grid} title=os.path.basename(filename).split('.')[0] outdir='output_bc03s' if not os.path.exists(outdir): os.mkdir(outdir)
lambB = lambB[0:2830] flux_t, lamb_t = glue_BR(spectrum_B, lambB, spectrum_R, lambR) spec_vor.append(flux_t) specNew, logLam, velscale = util.log_rebin([lamb_t[0], lamb_t[-1]], flux_t, oversample=False,flux=False) galaxy = specNew[np.where((np.exp(logLam) >= wave_limit[0]) & (np.exp(logLam) <= wave_limit[1]))] ##### pPXF: Here the actual fit starts. The best fit is plotted on the screen. noise = np.sqrt(np.var(galaxy)) noise = np.full_like(galaxy,noise) fig = plt.figure(num=0, figsize=(12.6, 5)) pp = ppxf(templates, galaxy, noise, velscale, start, plot=False, moments=moments, degree=-1,\ mdegree=10, vsyst=dv, clean=False, component=component, quiet=True) gas = np.array(component) == 1 # Select weights of gas emissions only stars = pp.matrix[:, ~gas].dot(pp.weights[~gas]) gas = pp.matrix[:, gas].dot(pp.weights[gas]) w = np.where(np.array(component) == 1)[0] filename = destdir+'/bins/'+'pPXFoutput_'+str(bin)+'.csv' dat = Table([wave, pp.galaxy, pp.bestfit, stars, gas], names=('wave', 'galaxy','bestfit', 'stars', 'gas')) dat.write(filename, format='csv', overwrite=True) fig, ax = plt.subplots(figsize = (17,15)) plt.subplot(211) plt.xlabel("Observed Wavelength ($\AA$)") plt.ylabel("Flux") plt.xlim([np.min(wave), np.max(wave)])
def run(self): if self.params.start is None: if not self.params.narrow_broad: start = [[self.vel, self.sig]] * (max(self.component) + 1) else: start = [[self.vel, self.sig]] * (max(self.component)/2 + 1) # Start narrow line component at half of broad line start.extend([[self.vel, self.sig/2]] * (max(self.component)/2)) else: start = self.params.start for i, e in enumerate(self.element): if start[i][0] is None: start[i][0] = self.vel if start[i][1] is None: if 'n_' not in e: start[i][1] = self.sig else: start[i][1] = self.sig/2. moments = [self.params.stellar_moments] + [self.params.gas_moments] * \ max(self.component) ## ----------============== The bestfit part ===============--------- ppxf.__init__(self, self.templates, self.bin_log, self.bin_log_noise, self.velscale, start, goodpixels=self.goodPixels, mdegree=self.params.mdegree, moments=moments, degree=self.params.degree, vsyst=self.dv, component=self.component, lam=self.lambdaq, plot=not self.params.quiet, quiet=self.params.quiet, produce_plot=False) if self.params.gas == 0: self.sol = [self.sol] self.error = [self.error] ## ----------===============================================--------- ## ----------================= The MC part =================--------- ## ----------===============================================--------- if self.params.use_all_temp is not None: self.MCstellar_kin = np.zeros((self.params.reps, abs(self.params.stellar_moments))) self.MCstellar_kin_err = np.zeros((self.params.reps, abs(self.params.stellar_moments))) self.MCbestfit_uncert = np.zeros((3, len(self.galaxy))) MCbestfit_mean = np.zeros(len(self.galaxy)) if self.params.gas: self.MCgas_kin = np.zeros((max(self.component), self.params.reps, abs(self.params.gas_moments))) self.MCgas_kin_err = np.zeros((max(self.component), self.params.reps, abs(self.params.gas_moments))) n_lines = len(self.e_templates.templatesToUse) # self.MCgas_weights = np.zeros((n_lines, self.params.reps)) self.MCgas_uncert_spec = np.zeros((n_lines, 3, len(self.galaxy))) MCgas_mean_spec = np.zeros((n_lines, len(self.galaxy))) if self.params.reps == 0: self.MCgas_uncert_spec = np.zeros((n_lines, len(self.galaxy))) else: self.MCgas_kin = None self.MCgas_kin_err = None # self.MCgas_weights = None for rep in range(self.params.reps): random = np.random.randn(len(self.bin_log_noise)) if self.params.use_residuals and rep == 0: _, residuals, _ = moving_weighted_average(self.lam, self.bestfit - self.bin_lin, step_size=3., interp=True) add_noise = random * np.sqrt(self.bin_log_noise**2 + residuals**2) elif self.params.use_residuals: _, residuals, _ = moving_weighted_average(self.lam, ppMC.bestfit - self.bin_lin, step_size=3., interp=True) add_noise = random * np.sqrt(self.bin_log_noise**2 + residuals**2) else: add_noise = random * np.abs(self.bin_log_noise) self.bin_log = self.bestfit + add_noise ppMC = ppxf(self.templates, self.bin_log, self.bin_log_noise, self.velscale, start, goodpixels=self.goodPixels, moments=moments, degree=self.params.degree, vsyst=self.dv, lam=self.lambdaq, plot=not self.params.quiet, quiet=self.params.quiet, bias=0.1, component=self.component, mdegree=self.params.mdegree) if self.params.gas == 0: ppMC.sol = [ppMC.sol] ppMC.error = [ppMC.error] if self.params.use_all_temp is not None: self.MCstellar_kin[rep,:] = ppMC.sol[0][ 0:abs(self.params.stellar_moments)] self.MCstellar_kin_err[rep,:] = ppMC.error[0][0: abs(self.params.stellar_moments)] # Find uncertainty in bestfit new_mean_bestfit_spec = ((rep + 1) * MCbestfit_mean + ppMC.bestfit)/( rep + 2) if rep < 3: # Save spec until 3 reps have been completed self.MCbestfit_uncert[rep, :] = ppMC.bestfit else: # Finding sigma_N from x_N, mean_N, mean_(N-1) and sigma_(N-1) # NB: rep = N-1 due to being zero-based self.MCbestfit_uncert = np.sqrt(((ppMC.bestfit - new_mean_bestfit_spec) * (ppMC.bestfit - MCbestfit_mean) + rep * self.MCbestfit_uncert**2)/(rep + 1)) MCbestfit_mean = np.array(new_mean_bestfit_spec) if rep == 2: # Calc std at 3rd rep self.MCbestfit_uncert = np.std(self.MCbestfit_uncert, axis=0) # Gas kinematics from all reps for g in range(len(self.element) - 1): self.MCgas_kin[g,rep,:] = ppMC.sol[g+1][0:abs(self.params.gas_moments)] self.MCgas_kin_err[g,rep,:] = ppMC.error[g+1][ 0:abs(self.params.gas_moments)] # Find uncertainty in fitted emission lines for i, n in enumerate(self.e_templates.templatesToUse): e_line_spec = ppMC.matrix[:, -n_lines + i] * ppMC.weights[ -n_lines + i] new_mean_gas_spec = ((rep + 1) * MCgas_mean_spec[i, :] + e_line_spec)/(rep + 2) if rep < 3 or self.params.reps == 0: self.MCgas_uncert_spec[i, rep, :] = e_line_spec else: self.MCgas_uncert_spec[i,:] = np.sqrt(((e_line_spec - new_mean_gas_spec) * (e_line_spec - MCgas_mean_spec[i,:]) + rep * self.MCgas_uncert_spec[i,:]**2)/(rep + 1)) MCgas_mean_spec[i, :] = np.array(new_mean_gas_spec) if rep == 2 and self.params.gas != 0: self.MCgas_uncert_spec = np.std(self.MCgas_uncert_spec, axis=1) if self.params.produce_plot: self.fig, self.ax = create_plot(self).produce
def ppxf_population_gas_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS_DR8.fits' hdu = fits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t['wavelength'] > 3540) & (t['wavelength'] < 7409) flux = t['flux'][mask] galaxy = flux / np.median( flux) # Normalize spectrum to avoid numerical issues wave = t['wavelength'][mask] # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0). # A constant error is not a bad approximation in the fitted wavelength # range and reduces the noise in the fit. # noise = galaxy * 0 + 0.01528 # 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]) FWHM_gal = 2.76 # SDSS has an approximate instrumental resolution FWHM of 2.76A. #------------------- Setup templates ----------------------- stars_templates, lamRange_temp, logLam_temp = \ setup_spectral_library(velscale, FWHM_gal) # The stellar templates are reshaped 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 = stars_templates.shape[1:] stars_templates = stars_templates.reshape(stars_templates.shape[0], -1) # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # stars_templates /= np.median( stars_templates) # Normalizes stellar templates by a scalar regul_err = 0.004 # Desired regularization error # Construct a set of Gaussian emission line templates. # Estimate the wavelength fitted range in the rest frame. # lamRange_gal = np.array([np.min(wave), np.max(wave)]) / (1 + z) gas_templates, line_names, line_wave = \ util.emission_lines(logLam_temp, lamRange_gal, FWHM_gal) # 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_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = c * np.log(lamRange_temp[0] / wave[0]) # km/s vel = c * np.log(1 + z) # Relation between redshift and velocity in pPXF # Here the actual fit starts. The best fit is plotted on the screen. # # 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. # start = [vel, 180., 0, 0] # (km/s), starting guess for [V,sigma] t = clock() # Assign component=0 to the stellar templates and # component=1 to the gas emission lines templates. # One can easily assign different kinematic components to different gas species # e.g. component=1 for the Balmer series, component=2 for the [OIII] doublet, ...) # Input a negative MOMENTS value to keep fixed the LOSVD of a component. # nTemps = stars_templates.shape[1] nLines = gas_templates.shape[1] component = [0] * nTemps + [1] * nLines moments = [4, 2] # fit (V,sig,h3,h4) for the stars and (V,sig) for the gas start = [start, start] # adopt the same starting value for both gas and stars pp = ppxf(templates, galaxy, noise, velscale, start, plot=False, moments=moments, degree=-1, mdegree=10, vsyst=dv, clean=False, regul=1. / regul_err, reg_dim=reg_dim, component=component) # Plot fit results for stars and gas plt.clf() plt.subplot(211) plt.plot(wave, pp.galaxy, 'k') plt.plot(wave, pp.bestfit, 'b', linewidth=2) plt.xlabel("Observed Wavelength ($\AA$)") plt.ylabel("Relative Flux") plt.ylim([-0.1, 1.3]) plt.xlim([np.min(wave), np.max(wave)]) plt.plot(wave, pp.galaxy - pp.bestfit, 'd', ms=4, color='LimeGreen', mec='LimeGreen') # fit residuals plt.axhline(y=-0, linestyle='--', color='k', linewidth=2) stars = pp.matrix[:, :nTemps].dot(pp.weights[:nTemps]) plt.plot(wave, stars, 'r', linewidth=2) # overplot stellar templates alone gas = pp.matrix[:, -nLines:].dot(pp.weights[-nLines:]) plt.plot(wave, gas + 0.15, 'b', linewidth=2) # overplot emission lines alone # 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)) w = np.array(component) == 1 # Extract weights of gas emissions only print('++++++++++++++++++++++++++++++') print('Gas V=%.4g and sigma=%.2g km/s' % (pp.sol[1][0], pp.sol[1][1])) # component=1 print('Emission lines peak intensity:') for name, weight, line in zip(line_names, pp.weights[w], pp.matrix[:, w].T): print('%12s: %.3g' % (name, weight * np.max(line))) print('------------------------------') # Plot stellar population mass distribution plt.subplot(212) weights = pp.weights[:np.prod(reg_dim)].reshape(reg_dim) / pp.weights.sum() plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', origin='upper', extent=(np.log10(1.0), np.log10(17.7828), -1.9, 0.45)) plt.colorbar() plt.title("Mass Fraction") plt.xlabel("log$_{10}$ Age (Gyr)") plt.ylabel("[M/H]") plt.tight_layout() plt.show()
def ppxf_example_two_components(): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure hdu = fits.open(file_dir + '/miles_models/Mun1.30Zp0.00T12.5893.fits' ) # Solar metallicitly, Age=12.59 Gyr gal_lin = hdu[0].data h1 = hdu[0].header lamRange1 = h1['CRVAL1'] + np.array( [0., h1['CDELT1'] * (h1['NAXIS1'] - 1)]) c = 299792.458 # speed of light in km/s velscale = c * h1['CDELT1'] / max( lamRange1) # Do not degrade original velocity sampling model1, logLam1, velscale = util.log_rebin(lamRange1, gal_lin, velscale=velscale) model1 /= np.median(model1) hdu = fits.open(file_dir + '/miles_models/Mun1.30Zp0.00T01.0000.fits' ) # Solar metallicitly, Age=1.00 Gyr gal_lin = hdu[0].data model2, logLam1, velscale = util.log_rebin(lamRange1, gal_lin, velscale=velscale) model2 /= np.median(model2) model = np.column_stack([model1, model2]) galaxy = np.empty_like(model) # These are the input values in spectral pixels # for the (V,sigma) of the two kinematic components # vel = np.array([0., 250.]) / velscale sigma = np.array([200., 100.]) / velscale # The synthetic galaxy model consists of the sum of two # SSP spectra with age of 1Gyr and 13Gyr respectively # with different velocity and dispersion # for j in range(len(vel)): dx = int(abs(vel[j]) + 4. * sigma[j]) # Sample the Gaussian at least to vel+4*sigma v = np.linspace(-dx, dx, 2 * dx + 1) losvd = np.exp(-0.5 * ((v - vel[j]) / sigma[j])**2) # Gaussian LOSVD losvd /= np.sum(losvd) # normaize LOSVD galaxy[:, j] = signal.fftconvolve(model[:, j], losvd, mode="same") galaxy[:, j] /= np.median(model[:, j]) galaxy = np.sum(galaxy, axis=1) sn = 100. noise = galaxy / sn galaxy = np.random.normal(galaxy, noise) # add noise to galaxy # Adopts two templates per kinematic component # templates = np.column_stack([model1, model2, model1, model2]) # Start both kinematic components from the same guess. # With multiple stellar kinematic components # a good starting guess is essential # start = [np.mean(vel) * velscale, np.mean(sigma) * velscale] start = [start, start] goodPixels = np.arange(20, 6000) t = clock() plt.clf() plt.subplot(211) plt.title("Two components pPXF fit") print("+++++++++++++++++++++++++++++++++++++++++++++") pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, degree=4, moments=[2, 2], component=[0, 0, 1, 1]) plt.subplot(212) plt.title("Single component pPXF fit") print("---------------------------------------------") start = start[0] pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, degree=4, moments=2) print("=============================================") print("Total elapsed time %.2f s" % (clock() - t)) plt.tight_layout() plt.pause(1)
def ppxf_simulation_example(): hdu = fits.open('miles_models/Mun1.30Zp0.00T12.5893.fits') # Solar metallicitly, Age=12.59 Gyr ssp = hdu[0].data h = hdu[0].header lamRange = h['CRVAL1'] + np.array([0.,h['CDELT1']*(h['NAXIS1']-1)]) c = 299792.458 # speed of light in km/s velscale = c*h['CDELT1']/max(lamRange) # Do not degrade original velocity sampling star, logLam, velscale = util.log_rebin(lamRange, ssp, velscale=velscale) # The finite sampling of the observed spectrum is modeled in detail: # the galaxy spectrum is obtained by oversampling the actual observed spectrum # to a high resolution. This represent the true spectrum, which is later resampled # to lower resolution to simulate the observations on the CCD. Similarly, the # convolution with a well-sampled LOSVD is done on the high-resolution spectrum, # and later resampled to the observed resolution before fitting with PPXF. factor = 10 # Oversampling integer factor for an accurate convolution starNew = ndimage.interpolation.zoom(star, factor, order=3) # This is the underlying spectrum, known at high resolution star = rebin(starNew, factor) # Make sure that the observed spectrum is the integral over the pixels np.random.seed(133) # for reproducible results h3 = 0.1 # Adopted G-H parameters of the LOSVD h4 = -0.1 sn = 30. # Adopted S/N of the Monte Carlo simulation m = 300 # Number of realizations of the simulation moments = 4 velV = np.random.rand(m) # velocity in *pixels* [=V(km/s)/velScale] sigmaV = np.linspace(0.5, 4, m) # Range of sigma in *pixels* [=sigma(km/s)/velScale] result = np.zeros((m, moments)) # This will store the results t = clock() for j, (vel, sigma) in enumerate(zip(velV, sigmaV)): dx = int(abs(vel) + 4.0*sigma) # Sample the Gaussian and GH at least to vel+4*sigma x = np.linspace(-dx, dx, 2*dx*factor + 1) # Evaluate the Gaussian using steps of 1/factor pixels. w = (x - vel)/sigma w2 = w**2 gauss = np.exp(-0.5*w2) gauss /= np.sum(gauss) # Normalized total(gauss)=1 h3poly = w*(2.*w2 - 3.)/np.sqrt(3.) # H3(y) h4poly = (w2*(4.*w2 - 12.) + 3.)/np.sqrt(24.) # H4(y) losvd = gauss *(1. + h3*h3poly + h4*h4poly) galaxy = signal.fftconvolve(starNew, losvd, mode="same") # Convolve the oversampled spectrum galaxy = rebin(galaxy, factor) # Integrate spectrum into original spectral pixels noise = galaxy/sn # 1sigma error spectrum galaxy = np.random.normal(galaxy, noise) # Add noise to the galaxy spectrum start = np.array([vel + np.random.random(), sigma*np.random.uniform(0.85, 1.15), 0, 0])*velscale # Convert to km/s pp = ppxf(star, galaxy, noise, velscale, start, goodpixels=np.arange(dx, galaxy.size - dx), plot=False, moments=moments, bias=0.3, oversample=None) result[j,:] = pp.sol print('Calculation time: %.2f s' % (clock()-t)) plt.clf() plt.subplot(221) plt.plot(sigmaV*velscale, (result[:,0]/velscale - velV)/sigmaV, '+k') plt.axhline(0, color='r') plt.axvline(velscale, linestyle='dashed') plt.axvline(2*velscale, linestyle='dashed') plt.ylim(-0.3, 0.3) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel(r'$(V - V_{\rm in})/\sigma_{\rm in}$') plt.text(2.05*velscale, -0.2, r'2$\times$velscale') plt.subplot(222) plt.plot(sigmaV*velscale, np.log10(result[:,1]/(velscale*sigmaV)), '+k') plt.axhline(0, color='r') plt.axvline(velscale, linestyle='dashed') plt.axvline(2*velscale, linestyle='dashed') plt.ylim(-0.15, 0.15) plt.xlabel(r'$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel(r'$\log(\sigma/\sigma_{\rm in})$') plt.text(2.05*velscale, -0.1, r'2$\times$velscale') plt.subplot(223) plt.plot(sigmaV*velscale, result[:,2], '+k') plt.axhline(h3, color='r') plt.axhline(0, linestyle='dotted', color='limegreen') plt.axvline(velscale, linestyle='dashed') plt.axvline(2*velscale, linestyle='dashed') plt.ylim(-0.15+h3, 0.15+h3) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel('$h_3$') plt.text(2.05*velscale, h3 - 0.1, r'2$\times$velscale') plt.subplot(224) plt.plot(sigmaV*velscale, result[:,3], '+k') plt.axhline(h4, color='r') plt.axhline(0, linestyle='dotted', color='limegreen') plt.axvline(velscale, linestyle='dashed') plt.axvline(2*velscale, linestyle='dashed') plt.ylim(-0.15+h4, 0.15+h4) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel('$h_4$') plt.text(2.05*velscale, h4 - 0.1, r'2$\times$velscale') plt.tight_layout() plt.pause(0.01)
def ppxf_kinematics_example_sauron(): # Read a galaxy spectrum and define the wavelength range # dir = 'spectra/' file = dir + 'NGC4550_SAURON.fits' hdu = pyfits.open(file) gal_lin = hdu[0].data h1 = hdu[0].header lamRange1 = h1['CRVAL1'] + np.array([0.,h1['CDELT1']*(h1['NAXIS1']-1)]) FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum before the usual LOG_REBIN, given that a # red shift corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_lin) galaxy = galaxy/np.median(galaxy) # Normalize spectrum to avoid numerical issues noise = galaxy*0 + 0.0049 # Assume constant noise per pixel here # Read the list of filenames from the Single Stellar Population library # by Vazdekis (1999, ApJ, 513, 224). A subset of the library is included # for this example with permission. See http://purl.org/cappellari/software # for suggestions of more up-to-date stellar libraries. # vazdekis = glob.glob(dir + 'Rbi1.30z*.fits') vazdekis.sort() FWHM_tem = 1.8 # Vazdekis spectra have a resolution FWHM of 1.8A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = pyfits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array([0.,h2['CDELT1']*(h2['NAXIS1']-1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size,len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels for j in range(len(vazdekis)): hdu = pyfits.open(vazdekis[j]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp,sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:,j] = sspNew/np.median(sspNew) # Normalizes 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 (see above). # c = 299792.458 dv = (logLam2[0]-logLam1[0])*c # km/s vel = 450. # Initial estimate of the galaxy velocity in km/s z = np.exp(vel/c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # 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. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
def ppxf_kinematics_gas(bin_sci, ppxf_file, ppxf_bestfit, template_fits, template_res, vel_init=1500., sig_init=100., bias=0.6, plot=False, quiet=True): """ Follow the pPXF usage example by Michile Cappellari INPUT: DIR_SCI_COMB (comb_fits_sci_{S/N}/bin_sci_{S/N}.fits), TEMPLATE_* (spectra/Mun1.30z*.fits) OUTPUT: PPXF_FILE (ppxf_output_sn30.txt), OUT_BESTFIT_FITS (ppxf_fits/ppxf_bestfit_sn30.fits) """ if os.path.exists(ppxf_file): print('File {} already exists'.format(ppxf_file)) return # Read in the galaxy spectrum, logarithmically rebin # assert len(glob.glob(bin_sci.format('*'))) > 0, 'Binned spectra {} not found'.format(glob.glob(bin_sci.format('*'))) with fits.open(bin_sci.format(0)) as gal_hdu: gal_data = gal_hdu[0].data gal_hdr = gal_hdu[0].header lamRange1 = gal_hdr['CRVAL1'] + np.array([1. - gal_hdr['CRPIX1'], gal_hdr['NAXIS1'] - gal_hdr['CRPIX1']]) \ * gal_hdr['CD1_1'] galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_data) galaxy = galaxy / np.median(galaxy) # Normalize spectrum to avoid numerical issues wave = np.exp(logLam1) # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0). # A constant error is not a bad approximation in the fitted wavelength # range and reduces the noise in the fit. # noise = galaxy * 0 + 0.01528 # Assume constant noise per pixel here c = 299792.458 # speed of light in km/s FWHM_gal = 2.3 # GMOS IFU has an instrumental resolution FWHM of 2.3 A # stars_templates, lamRange_temp, logLam_temp = setup_spectral_library(velscale, FWHM_gal) # Read in template galaxy spectra # template_spectra = glob.glob(template_fits) assert len(template_spectra) > 0, 'Template spectra not found: {}'.format(template_fits) with fits.open(template_spectra[0]) as temp_hdu: ssp = temp_hdu[0].data h2 = temp_hdu[0].header lamRange2 = h2['CRVAL1'] + np.array([0., h2['CDELT1'] * (h2['NAXIS1'] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) stars_templates = np.empty((sspNew.size, len(template_spectra))) FWHM_tem = template_res # FWHM_dif = np.sqrt(FWHM_gal ** 2 - template_res ** 2) FWHM_dif = np.sqrt(FWHM_tem ** 2 - FWHM_gal ** 2) sigma = FWHM_dif / 2.355 / h2['CDELT1'] # SIGMA DIFFERENCE IN PIXELS, 1.078435697220085 for j in range(len(template_spectra)): with fits.open(template_spectra[j]) as temp_hdu_j: ssp_j = temp_hdu_j[0].data ssp_j = ndimage.gaussian_filter1d(ssp_j, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp_j, velscale=velscale) stars_templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates # we save the original array dimensions, which are needed to specify the regularization dimensions # reg_dim = stars_templates.shape[1:] # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # regul_err = 0.004 # Desired regularization error # Construct a set of Gaussian emission line templates. # Estimate the wavelength fitted range in the rest frame. # z = np.exp(vel_init / c) - 1 # Relation between velocity and redshift in pPXF lamRange_gal = np.array([lamRange1[0], lamRange1[-1]]) / (1 + z) gas_templates, line_names, line_wave = util.emission_lines(logLam2, lamRange_gal, FWHM_gal) # 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.hstack([stars_templates, gas_templates]) # Here the actual fit starts. The best fit is plotted on the screen. # # 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. # # start = [vel, 180.] # (km/s), starting guess for [V,sigma] start = [vel_init, sig_init] # (km/s), starting guess for [V,sigma] # Assign component=0 to the stellar templates and # component=1 to the gas emission lines templates. # One can easily assign different kinematic components to different gas species # e.g. component=1 for the Balmer series, component=2 for the [OIII] doublet, ...) # Input a negative MOMENTS value to keep fixed the LOSVD of a component. # nTemps = stars_templates.shape[1] nLines = gas_templates.shape[1] component = [0] * nTemps + [1] * nLines moments = [4, 2] # fit (V,sig,h3,h4) for the stars and (V,sig) for the gas start = [start, start] # adopt the same starting value for both gas and stars bin_sci_list = glob.glob(bin_sci.format('*')) pp_sol = np.zeros([moments[0] + moments[1], len(bin_sci_list)]) pp_error = np.zeros([moments[0] + moments[1], len(bin_sci_list)]) for j in range(len(bin_sci_list)): gal_data = fits.getdata(bin_sci.format(j), 0) gal_data_new = ndimage.gaussian_filter1d(gal_data, sigma) galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_data_new, velscale=velscale) noise = galaxy * 0 + 1 # Assume constant noise per pixel here # 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_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, plot=plot, moments=moments, degree=-1, mdegree=10, vsyst=dv, clean=False, regul=1. / regul_err, reg_dim=reg_dim, component=component, bias=bias, quiet=quiet) # Save the velocity, sigma, h3, h4 information for both stellar and gas to a table for k, sol in enumerate(pp.sol[0]): pp_sol[k, j] = pp.sol[0][k] pp_error[k, j] = pp.error[0][k] for k, sol in enumerate(pp.sol[1]): pp_sol[k + len(pp.sol[0]), j] = pp.sol[1][k] pp_error[k + len(pp.error[0]), j] = pp.error[1][k] # Plot fit results for stars and gas # if plot: plt.clf() # plt.subplot(211) plt.plot(wave, pp.galaxy, 'k') plt.plot(wave, pp.bestfit, 'b', linewidth=2) plt.xlabel("Observed Wavelength ($\AA$)") plt.ylabel("Relative Flux") plt.ylim([-0.1, 1.3]) plt.xlim([np.min(wave), np.max(wave)]) plt.plot(wave, pp.galaxy - pp.bestfit, 'd', ms=4, color='LimeGreen', mec='LimeGreen') # fit residuals plt.axhline(y=-0, linestyle='--', color='k', linewidth=2) stars = pp.matrix[:, :nTemps].dot(pp.weights[:nTemps]) plt.plot(wave, stars, 'r', linewidth=2) # overplot stellar templates alone gas = pp.matrix[:, -nLines:].dot(pp.weights[-nLines:]) plt.plot(wave, gas + 0.15, 'b', linewidth=2) # overplot emission lines alone plt.legend() plt.show() # 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)) w = np.where(np.array(component) == 1)[0] # Extract weights of gas emissions print('++++++++++++++++++++++++++++++') print('Gas V=%.4g and sigma=%.2g km/s' % (pp.sol[1][0], pp.sol[1][1])) print('Emission lines peak intensity:') for name, weight, line in zip(line_names, pp.weights[w], pp.matrix[:, w].T): print('%12s: %.3g' % (name, weight * np.max(line))) print('------------------------------') # Plot stellar population mass distribution # plt.subplot(212) # weights = pp.weights[:np.prod(reg_dim)].reshape(reg_dim) / pp.weights.sum() # plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', origin='upper', # extent=(np.log10(1.0), np.log10(17.7828), -1.9, 0.45)) # plt.colorbar() # plt.title("Mass Fraction") # plt.xlabel("log$_{10}$ Age (Gyr)") # plt.ylabel("[M/H]") # plt.tight_layout() plt.legend() plt.show() if not quiet: print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) hdu_best = fits.PrimaryHDU() hdu_best.data = pp.bestfit hdu_best.header['CD1_1'] = gal_hdr['CD1_1'] hdu_best.header['CDELT1'] = gal_hdr['CD1_1'] hdu_best.header['CRPIX1'] = gal_hdr['CRPIX1'] hdu_best.header['CRVAL1'] = gal_hdr['CRVAL1'] hdu_best.header['NAXIS1'] = pp.bestfit.size hdu_best.header['CTYPE1'] = 'LINEAR' # corresponds to sampling of values above hdu_best.header['DC-FLAG'] = '1' # 0 = linear, 1= log-linear sampling hdu_best.writeto(ppxf_bestfit.format(j), clobber=True) np.savetxt(ppxf_file, np.column_stack( [pp_sol[0], pp_error[0], pp_sol[1], pp_error[1], pp_sol[2], pp_error[2], pp_sol[3], pp_error[3], pp_sol[4], pp_error[4], pp_sol[5], pp_error[5]]), fmt=b'%10.4f %10.4f %10.4f %10.4f %10.4f %10.4f %10.4f %10.4f %10.4f %10.4f %10.4f %10.4f', header='Stellar pop: Gas: \n' 'vel d_vel sigma d_sigma h3 d_h3 h4 d_h4 vel d_vel sigma d_sigma')
def ppxf_simulation(ppxf_bestfit, lam_range, target_sn, bias=0.6, spaxel=0): """ 2. Perform a fit of your kinematics *without* penalty (PPXF keyword BIAS=0). The solution will be noisy and may be affected by spurious solutions, however this step will allow you to check the expected mean ranges in the Gauss-Hermite parameters [h3,h4] for the galaxy under study; see ppxf_output_sn30_bias0.txt mean(h3), std(h3), max(h3) = -0.2555, 0.09090, 0.007468 # ignoring endpoints, max(h3[1:-1]) = -0.01376 mean(h4), std(h4), max(h4) = -0.07712, 0.1423, 0.136607 # ignoring endpoints, max(h4[1,-1]) = 0.13594 max(dvel), min(dvel), max(dvel)-np.min(dvel) = 119.2918, 4.08643, 115.20544 mean(vel) = 1543.0359 max(sig), min(sig) = 180.3, 36.23 3. Perform a Monte Carlo simulation of your spectra, following e.g. the included ppxf_simulation_example.pro routine. Adopt as S/N in the simulation the chosen value (S/N)_min and as input [h3,h4] the maximum representative values measured in the non-penalized pPXF fit of the previous step; 4. Choose as penalty (BIAS) the *largest* value such that, for sigma > 3*velScale, the mean difference between the output [h3,h4] and the input [h3,h4] is well within the rms scatter of the simulated values (see e.g. Fig.2 of Emsellem et al. 2004, MNRAS, 352, 721). """ # dir = 'spectra/' # file = dir + 'Rbi1.30z+0.00t12.59.fits' # hdu = pyfits.open(file) # ssp = hdu[0].data # h = hdu[0].header bestfit_file = ppxf_bestfit.format(spaxel) assert os.path.exists(bestfit_file), 'Best fit spectra not found: {}'.format(bestfit_file) with fits.open(bestfit_file) as best_hdu: ssp = best_hdu[0].data h = best_hdu[0].header # lamRange = h['CRVAL1'] + np.array([0., h['CDELT1'] * (h['NAXIS1'] - 1)]) # star, logLam, velscale = util.log_rebin(lamRange, ssp) star, logLam, velscale = util.log_rebin(lam_range, ssp) # The finite sampling of the observed spectrum is modeled in detail: the galaxy spectrum is obtained by oversampling # the actual observed spectrum to a high resolution. This represents the true spectrum, which is later resampled # to lower resolution to simulate the observations on the CCD. Similarly, the convolution with a well-sampled LOSVD # is done on the high-resolution spectrum, and later resampled to the observed resolution before fitting with PPXF. factor = 10 # Oversampling integer factor for an accurate convolution starNew = ndimage.interpolation.zoom(star, factor, order=1) # The underlying spectrum, known at high resolution star = rebin(starNew, factor) # Make sure that the observed spectrum is the integral over the pixels # vel = 0.3 # velocity in *pixels* [=V(km/s)/velScale] # h3 = 0.1 # Adopted G-H parameters of the LOSVD # h4 = -0.1 # sn = 60. # Adopted S/N of the Monte Carlo simulation # m = 300 # Number of realizations of the simulation # sigmaV = np.linspace(0.8, 4, m) # Range of sigma in *pixels* [=sigma(km/s)/velScale] print('bestfit {} : velscale = {}'.format(spaxel, velscale)) # bestfit 0 : velscale = 57.44245804 # max(sig), min(sig) = 164.026343, 11.306016 [km/s] = 2.87765514, 0.19835116 [pix] vel = 0.3 # mean vel = 1556.4989, I have no idea why this value is 0.3 ... h3 = -0.003146 h4 = -0.003662 sn = target_sn m = 300 # Number of realizations of the simulation sigmaV = np.linspace(0.8, 4, m) # Range of sigma in *pixels* [=sigma(km/s)/velScale] # sigmaV = np.linspace(0.198, 2.877, m) result = np.zeros((m, 4)) # This will store the results t = clock() np.random.seed(123) # for reproducible results for j in range(m): sigma = sigmaV[j] dx = int(abs(vel) + 4.0 * sigma) # Sample the Gaussian and GH at least to vel+4*sigma x = np.linspace(-dx, dx, 2 * dx * factor + 1) # Evaluate the Gaussian using steps of 1/factor pixels. w = (x - vel) / sigma w2 = w ** 2 gauss = np.exp(-0.5 * w2) / (np.sqrt(2. * np.pi) * sigma * factor) # Normalized total(gauss)=1 h3poly = w * (2. * w2 - 3.) / np.sqrt(3.) # H3(y) h4poly = (w2 * (4. * w2 - 12.) + 3.) / np.sqrt(24.) # H4(y) losvd = gauss * (1. + h3 * h3poly + h4 * h4poly) galaxy = signal.fftconvolve(starNew, losvd, mode="same") # Convolve the oversampled spectrum galaxy = rebin(galaxy, factor) # Integrate spectrum into original spectral pixels noise = galaxy / sn # 1sigma error spectrum galaxy = np.random.normal(galaxy, noise) # Add noise to the galaxy spectrum start = np.array( [vel + np.random.random(), sigma * np.random.uniform(0.85, 1.15)]) * velscale # Convert to km/s pp = ppxf(star, galaxy, noise, velscale, start, goodpixels=np.arange(dx, galaxy.size - dx), plot=False, moments=4, bias=bias, quiet=True) result[j, :] = pp.sol print('Calculation time: %.2f s' % (clock() - t)) plt.clf() plt.subplot(221) plt.plot(sigmaV * velscale, result[:, 0] - vel * velscale, '+k') plt.plot(sigmaV * velscale, np.ones(len(sigmaV * velscale)) * np.mean(result[:, 0] - vel * velscale), '-b') plt.plot(sigmaV * velscale, sigmaV * velscale * 0, '-r') plt.ylim(-40, 40) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$V - V_{in}\ (km\ s^{-1}$)') plt.subplot(222) plt.plot(sigmaV * velscale, result[:, 1] - sigmaV * velscale, '+k') plt.plot(sigmaV * velscale, np.ones(len(sigmaV * velscale)) * np.mean(result[:, 1] - sigmaV * velscale), '-b') plt.plot(sigmaV * velscale, sigmaV * velscale * 0, '-r') plt.ylim(-40, 40) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$\sigma - \sigma_{in}\ (km\ s^{-1}$)') plt.subplot(223) plt.plot(sigmaV * velscale, result[:, 2], '+k') plt.plot(sigmaV * velscale, sigmaV * velscale * 0 + h3, '-r') plt.plot(sigmaV * velscale, np.ones(len(sigmaV * velscale)) * np.mean(result[:, 2]), '-b') plt.ylim(-0.2 + h3, 0.2 + h3) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$h_3$') plt.subplot(224) plt.plot(sigmaV * velscale, result[:, 3], '+k') plt.plot(sigmaV * velscale, sigmaV * velscale * 0 + h4, '-r') plt.plot(sigmaV * velscale, np.ones(len(sigmaV * velscale)) * np.mean(result[:, 3]), '-b') plt.ylim(-0.2 + h4, 0.2 + h4) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$h_4$') plt.tight_layout() plt.show()
def ppxf_simulation_example(): dir = 'spectra/' file = dir + 'Rbi1.30z+0.00t12.59.fits' hdu = pyfits.open(file) ssp = hdu[0].data h = hdu[0].header lamRange = h['CRVAL1'] + np.array([0.,h['CDELT1']*(h['NAXIS1']-1)]) star, logLam, velscale = util.log_rebin(lamRange, ssp) # The finite sampling of the observed spectrum is modeled in detail: # the galaxy spectrum is obtained by oversampling the actual observed spectrum # to a high resolution. This represent the true spectrum, which is later resampled # to lower resolution to simulate the observations on the CCD. Similarly, the # convolution with a well-sampled LOSVD is done on the high-resolution spectrum, # and later resampled to the observed resolution before fitting with PPXF. factor = 10 # Oversampling integer factor for an accurate convolution starNew = ndimage.interpolation.zoom(star,factor,order=1) # This is the underlying spectrum, known at high resolution star = rebin(starNew,factor) # Make sure that the observed spectrum is the integral over the pixels vel = 0.3 # velocity in *pixels* [=V(km/s)/velScale] h3 = 0.1 # Adopted G-H parameters of the LOSVD h4 = -0.1 sn = 60. # Adopted S/N of the Monte Carlo simulation m = 300 # Number of realizations of the simulation sigmaV = np.linspace(0.8,4,m) # Range of sigma in *pixels* [=sigma(km/s)/velScale] result = np.zeros((m,4)) # This will store the results t = clock() np.random.seed(123) # for reproducible results for j in range(m): sigma = sigmaV[j] dx = int(abs(vel)+4.0*sigma) # Sample the Gaussian and GH at least to vel+4*sigma x = np.linspace(-dx,dx,2*dx*factor+1) # Evaluate the Gaussian using steps of 1/factor pixels. w = (x - vel)/sigma w2 = w**2 gauss = np.exp(-0.5*w2)/(np.sqrt(2.*np.pi)*sigma*factor) # Normalized total(gauss)=1 h3poly = w*(2.*w2 - 3.)/np.sqrt(3.) # H3(y) h4poly = (w2*(4.*w2 - 12.) + 3.)/np.sqrt(24.) # H4(y) losvd = gauss *(1. + h3*h3poly + h4*h4poly) galaxy = signal.fftconvolve(starNew,losvd,mode="same") # Convolve the oversampled spectrum galaxy = rebin(galaxy,factor) # Integrate spectrum into original spectral pixels noise = galaxy/sn # 1sigma error spectrum galaxy = np.random.normal(galaxy, noise) # Add noise to the galaxy spectrum start = np.array([vel+np.random.random(), sigma*np.random.uniform(0.85,1.15)])*velscale # Convert to km/s pp = ppxf(star, galaxy, noise, velscale, start, goodpixels=np.arange(dx,galaxy.size-dx), plot=False, moments=4, bias=0.5) result[j,:] = pp.sol print('Calculation time: %.2f s' % (clock()-t)) plt.clf() plt.subplot(221) plt.plot(sigmaV*velscale, result[:,0]-vel*velscale, '+k') plt.plot(sigmaV*velscale, sigmaV*velscale*0, '-r') plt.ylim(-40, 40) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$V - V_{in}\ (km\ s^{-1}$)') plt.subplot(222) plt.plot(sigmaV*velscale, result[:,1]-sigmaV*velscale, '+k') plt.plot(sigmaV*velscale, sigmaV*velscale*0, '-r') plt.ylim(-40, 40) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$\sigma - \sigma_{in}\ (km\ s^{-1}$)') plt.subplot(223) plt.plot(sigmaV*velscale, result[:,2], '+k') plt.plot(sigmaV*velscale, sigmaV*velscale*0+h3, '-r') plt.ylim(-0.2+h3, 0.2+h3) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$h_3$') plt.subplot(224) plt.plot(sigmaV*velscale, result[:,3], '+k') plt.plot(sigmaV*velscale, sigmaV*velscale*0+h4, '-r') plt.ylim(-0.2+h4, 0.2+h4) plt.xlabel('$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel('$h_4$') plt.tight_layout() plt.pause(0.01)
def interface_sets(nstart, nend): full_time = clock() ####### setting up some variables for magnitudes and indices calculation ######## c = 299792.458 filter_list = 'sdss_manga_pipeline.res' zero_point = 'AB' redshift = 0.0 indices_list = 'MaNGA_range.def' print(' > gathering info') ######## setting up directories and files ######## data_dir = '/mnt/lustre/smg/stellar_libraries/MaNGA/' output_dir = '/mnt/lustre/smg/stellar_libraries/MaNGA/parameters/she-ra/beta_tests/combo_test/' file_spectra = 'MaStar_spectra_ebv_gaia_rb_isopars' ##### templates information ##### templates_dir = '/mnt/lustre/smg/stellar_libraries/atlas9marcs/' templates_parameters = 'templates_grid_parameters_R2000.fits' header = fits.open(templates_dir + templates_parameters) templ_id, templ_teff, templ_logg, templ_metal = header[1].data[ 'NAME'], header[1].data['TEFF'], header[1].data['LOGG'], header[ 1].data['METAL'] templ_gmag, templa_rmag, templ_imag = header[1].data['Gmag'], header[ 1].data['Rmag'], header[1].data['Imag'] tot_templ = len(templ_id) estimate_teff = interp1d(templ_gmag - templ_imag, templ_teff, bounds_error=False, fill_value='extrapolate') ######## reading spectra file ######## print(' > reading information from the spectra file') header = fits.open(data_dir + 'spectra/' + file_spectra + '.fits') mangaid, mastarid, starid = header[1].data['mangaid'], header[1].data[ 'mastarid'], header[1].data['id'] plate, ifudesign, mjd = header[1].data['plate'], header[1].data[ 'ifudesign'], header[1].data['mjd'] ra, dec = header[1].data['objra'], header[1].data['objdec'] wave, flux = header[1].data['wave'], header[1].data['flux'] minlogg = header[1].data['minlogg'] bad_info = np.where(minlogg <= -8) minlogg[bad_info] = 'NaN' nspectra = int(header[1].header['naxis2']) naxis1 = int(np.shape(wave)[1]) crval1 = min(wave[0, :]) crval2 = max(wave[0, :]) cdelt1 = abs(crval1 - crval2) / naxis1 new_wave = np.linspace(start=crval1, stop=crval2, num=naxis1) ######## setting up arrays ######## parameters = np.zeros((nspectra, 9)) selected_parameters = np.zeros((nspectra, 3)) ppxf_info = np.zeros((nspectra, 3)) indices = np.zeros((nspectra, 42)) mags = np.zeros((nspectra, 5)) ############################################################################################################################################# ######## running things for each spectrum ######## print(' > working on each spectrum') for i in range(nstart, nend + 1): start_time = clock() new_flux = np.interp(new_wave, wave[i, :], flux[i, :]) new_flux = new_flux * 1e-17 # to have 10^-17 erg/s/cm2/Angstrom as units ######## calculating the E(B-V) from Schlegel+98 as a function of coordinates ######## print(' > calculating the E(B-V) from Schlegel+98') ebv = get_dust_radec(ra[i], dec[i], 'ebv') ######## calculating the reddening as a function of wavelength ######## print(' > calculating the reddening') reddening = dust_allen_py(ebv, new_wave) ######## in order to correct, the flux needs to be divided by the reddening ######## corrected_flux = new_flux / reddening bad_flux = np.isnan(corrected_flux) | np.isinf(corrected_flux) | ( corrected_flux <= 0.0) corrected_flux[bad_flux] = 0.0 ######## calculating the indices ######## print( ' > calculating indices within the MaNGA wavelength range' ) indices[i, :], err_indices = calculate_indices(new_wave, corrected_flux, indices_list, mastarid[i], ef=0, rv=0, plot=False, sim=False) ######## calculating the magnitudes ######## print(' > calculating SDSS magnitudes') mags[i, :] = calculate_magnitudes(new_wave, corrected_flux, filter_list, zero_point, redshift) ######## calculating the first guess for the stellar parameters ######## print( ' > calculating the first set of teff, logg, and metallicity' ) parameters[i, 0] = estimate_teff( mags[i, 1] - mags[i, 3]) # Teff as a function of (g-i) parameters[i, 1] = minlogg[i] # from isoparsfit - isochrone fitting parameters[i, 2] = -1.0 # open metallicity bad_data = np.isnan(parameters[i, 0]) | np.isinf( parameters[i, 0]) | np.isnan(parameters[i, 1]) | np.isinf( parameters[i, 1]) | np.isnan(parameters[i, 2]) | np.isinf( parameters[i, 2]) if bad_data: parameters[i, :] = -999 ppxf_info[i, :] = -999 ##### making a preliminary output file ##### print(' > making a preliminary output file') f = open(output_dir + 'prelim_output/' + mastarid[i] + '_she-ra', 'w') f.write('%r %r %r %i %i %i %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n' \ %(mangaid[i], mastarid[i], starid[i], \ plate[i], ifudesign[i], mjd[i], \ ra[i],dec[i], \ parameters[i,0],parameters[i,1],parameters[i,2], \ parameters[i,3],parameters[i,4],parameters[i,5], \ parameters[i,6],parameters[i,7],parameters[i,8], \ ppxf_info[i,0], ppxf_info[i,1], ppxf_info[i,2], \ mags[i,0], mags[i,1], mags[i,2], mags[i,3], mags[i,4], \ indices[i,0], indices[i,1], indices[i,2], indices[i,3], indices[i,4], \ indices[i,5], indices[i,6], indices[i,7], indices[i,8], indices[i,9], \ indices[i,10], indices[i,11], indices[i,12], indices[i,13], indices[i,14], \ indices[i,15], indices[i,16], indices[i,17], indices[i,18], indices[i,19], \ indices[i,20], indices[i,21], indices[i,22], indices[i,23], indices[i,24], \ indices[i,25], indices[i,26], indices[i,27], indices[i,28], indices[i,29], \ indices[i,30], indices[i,31], indices[i,32], indices[i,33], indices[i,34], \ indices[i,35], indices[i,36], indices[i,37], indices[i,38], indices[i,39], \ indices[i,40], indices[i,41])) f.close() print(' > spectrum ' + str(i) + ' done') continue print(' > Teff = ' + str(parameters[i, 0]) + ' K') print(' > logg = ' + str(parameters[i, 1]) + ' dex') print(' > metal = ' + str(parameters[i, 2]) + ' dex') # not a real prior ######## calculating the second guess for the stellar parameters ######## if (parameters[i, 0] >= 12000): teff_guess = 2000 if (parameters[i, 0] < 12000): teff_guess = 1000 logg_guess = 0.80 # fixed value metal_guess = 2.00 # open metallicity selected_parameters[i, 0] = parameters[i, 0] selected_parameters[i, 1] = parameters[i, 1] selected_parameters[i, 2] = parameters[i, 2] if (parameters[i, 0] <= min(templ_teff)): selected_parameters[i, 0] = min(templ_teff) + teff_guess if (parameters[i, 0] >= max(templ_teff)): selected_parameters[i, 0] = max(templ_teff) - teff_guess if ((parameters[i, 0] >= 12000) & (parameters[i, 1] <= 3.5)): selected_parameters[i, 1] = 3.5 if ((parameters[i, 0] >= 8000) & (parameters[i, 0] <= 12000) & (parameters[i, 1] <= 2.0)): selected_parameters[i, 1] = 2.5 if ((parameters[i, 0] >= 6000) & (parameters[i, 0] <= 8000) & (parameters[i, 1] <= 1.0)): selected_parameters[i, 1] = 1.5 if ((parameters[i, 0] >= 2500) & (parameters[i, 0] <= 6000) & (parameters[i, 1] <= 0.0)): selected_parameters[i, 1] = 0.0 if ((parameters[i, 0] >= 2500) & (parameters[i, 0] <= 12000) & (parameters[i, 1] >= 5.0)): selected_parameters[i, 1] = 5.0 if (parameters[i, 2] <= min(templ_metal)): selected_parameters[i, 2] = min(templ_metal) + metal_guess if (parameters[i, 2] >= max(templ_metal)): selected_parameters[i, 2] = max(templ_metal) - metal_guess ok_set = np.where( (np.abs(templ_teff - selected_parameters[i, 0]) <= teff_guess) & (np.abs(templ_logg - selected_parameters[i, 1]) <= logg_guess) & (np.abs(templ_metal - selected_parameters[i, 2]) <= metal_guess)) templ_set_id = templ_id[ok_set] templ_set_teff = templ_teff[ok_set] templ_set_logg = templ_logg[ok_set] templ_set_metal = templ_metal[ok_set] if not templ_set_id.size: parameters[i, 3] = -999 parameters[i, 4] = -999 parameters[i, 5] = -999 parameters[i, 6] = -999 parameters[i, 7] = -999 parameters[i, 8] = -999 ##### making a preliminary output file ##### print(' > making a preliminary output file') f = open(output_dir + 'prelim_output/' + mastarid[i] + '_she-ra', 'w') f.write('%r %r %r %i %i %i %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n' \ %(mangaid[i], mastarid[i], starid[i], \ plate[i], ifudesign[i], mjd[i], \ ra[i],dec[i], \ parameters[i,0],parameters[i,1],parameters[i,2], \ parameters[i,3],parameters[i,4],parameters[i,5], \ parameters[i,6],parameters[i,7],parameters[i,8], \ ppxf_info[i,0], ppxf_info[i,1], ppxf_info[i,2], \ mags[i,0], mags[i,1], mags[i,2], mags[i,3], mags[i,4], \ indices[i,0], indices[i,1], indices[i,2], indices[i,3], indices[i,4], \ indices[i,5], indices[i,6], indices[i,7], indices[i,8], indices[i,9], \ indices[i,10], indices[i,11], indices[i,12], indices[i,13], indices[i,14], \ indices[i,15], indices[i,16], indices[i,17], indices[i,18], indices[i,19], \ indices[i,20], indices[i,21], indices[i,22], indices[i,23], indices[i,24], \ indices[i,25], indices[i,26], indices[i,27], indices[i,28], indices[i,29], \ indices[i,30], indices[i,31], indices[i,32], indices[i,33], indices[i,34], \ indices[i,35], indices[i,36], indices[i,37], indices[i,38], indices[i,39], \ indices[i,40], indices[i,41])) f.close() print(' > spectrum ' + str(i) + ' done') continue ##### setting up the MaStar for pPXF ##### velscale = c * cdelt1 / max( new_wave) # do not degrade original velocity sampling log_corrected_flux, log_new_wave, velscale = util.log_rebin( [min(new_wave), max(new_wave)], corrected_flux, velscale=velscale) ##### setting up the selected templates ##### templ_set = len(templ_set_id) templates = np.zeros((len(log_corrected_flux), templ_set)) for j in range(0, templ_set): hdu = fits.open(templates_dir + 'grid/' + templ_set_id[j] + '.fits') template_wave = np.linspace(start=hdu[0].header['CRVAL1'], stop=hdu[0].header['CRVAL2'], num=hdu[0].header['NAXIS1']) template_flux = np.interp(new_wave, template_wave, hdu[0].data) bad_flux = np.isnan(template_flux) | np.isinf(template_flux) | ( template_flux <= 0.0) template_flux[bad_flux] = 0.0 log_template_flux, log_template_wave, velscale = util.log_rebin( [min(new_wave), max(new_wave)], template_flux, velscale=velscale) log_template_flux /= np.median(log_template_flux) templates[:, j] = log_template_flux ##### setting up the variables to run pPXF ##### start = [0, 10] noise = np.ones_like(log_corrected_flux) sol = ppxf(templates, log_corrected_flux, noise, velscale, start, lam=np.exp(log_new_wave), degree=10, moments=2, quiet=True) sol.plot() plt.title(mastarid[i]) #plt.show() plt.savefig(output_dir + 'fsf/' + mastarid[i] + '_bestfit.png') plt.close() ##### gathering the chi2, vel and sigma from the pPXF output ##### print( ' > gathering the information from the full-spectrum fitting' ) ppxf_info[i, 0] = sol.chi2 ppxf_info[i, 1] = sol.sol[0] ppxf_info[i, 2] = sol.sol[1] print(' > chi2/dof = ' + str(ppxf_info[i, 0])) print(' > vel = ' + str(ppxf_info[i, 1]) + ' km s-1') print(' > sigma = ' + str(ppxf_info[i, 2]) + ' km s-1') prelim_teff = [] prelim_logg = [] prelim_metal = [] ppxf_weights = sol.weights[:] ##### calculating the weighted parameters ##### for j in range(0, templ_set): prelim_teff.append(sol.weights[j] * templ_set_teff[j] / np.sum(sol.weights[:])) prelim_logg.append(sol.weights[j] * templ_set_logg[j] / np.sum(sol.weights[:])) prelim_metal.append(sol.weights[j] * templ_set_metal[j] / np.sum(sol.weights[:])) parameters[i, 3] = np.sum(prelim_teff[:]) parameters[i, 4] = np.sum(prelim_logg[:]) parameters[i, 5] = np.sum(prelim_metal[:]) + 0.3 bad_data = np.isnan(parameters[i, 3]) | np.isinf( parameters[i, 3]) | np.isnan(parameters[i, 4]) | np.isinf( parameters[i, 4]) | np.isnan(parameters[i, 5]) | np.isinf( parameters[i, 5]) if bad_data: parameters[i, 3] = -999 parameters[i, 4] = -999 parameters[i, 5] = -999 parameters[i, 6] = -999 parameters[i, 7] = -999 parameters[i, 8] = -999 ##### making a preliminary output file ##### print(' > making a preliminary output file') f = open(output_dir + 'prelim_output/' + mastarid[i] + '_she-ra', 'w') f.write('%r %r %r %i %i %i %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n' \ %(mangaid[i], mastarid[i], starid[i], \ plate[i], ifudesign[i], mjd[i], \ ra[i],dec[i], \ parameters[i,0],parameters[i,1],parameters[i,2], \ parameters[i,3],parameters[i,4],parameters[i,5], \ parameters[i,6],parameters[i,7],parameters[i,8], \ ppxf_info[i,0], ppxf_info[i,1], ppxf_info[i,2], \ mags[i,0], mags[i,1], mags[i,2], mags[i,3], mags[i,4], \ indices[i,0], indices[i,1], indices[i,2], indices[i,3], indices[i,4], \ indices[i,5], indices[i,6], indices[i,7], indices[i,8], indices[i,9], \ indices[i,10], indices[i,11], indices[i,12], indices[i,13], indices[i,14], \ indices[i,15], indices[i,16], indices[i,17], indices[i,18], indices[i,19], \ indices[i,20], indices[i,21], indices[i,22], indices[i,23], indices[i,24], \ indices[i,25], indices[i,26], indices[i,27], indices[i,28], indices[i,29], \ indices[i,30], indices[i,31], indices[i,32], indices[i,33], indices[i,34], \ indices[i,35], indices[i,36], indices[i,37], indices[i,38], indices[i,39], \ indices[i,40], indices[i,41])) f.close() print(' > spectrum ' + str(i) + ' done') continue print( ' > calculating the second set of teff, logg, and metallicity' ) print(' > Teff = ' + str(parameters[i, 3]) + ' K') print(' > logg = ' + str(parameters[i, 4]) + ' dex') print(' > metal = ' + str(parameters[i, 5]) + ' dex') ##### gathering the parameters of the template with the largest weight ##### print( ' > calculating the third set of teff, logg, and metallicity' ) ok_heavy = np.where(ppxf_weights == max(ppxf_weights)) parameters[i, 6] = templ_set_teff[ok_heavy] parameters[i, 7] = templ_set_logg[ok_heavy] parameters[i, 8] = templ_set_metal[ok_heavy] + 0.3 print(' > Teff = ' + str(parameters[i, 6]) + ' K') print(' > logg = ' + str(parameters[i, 7]) + ' dex') print(' > metal = ' + str(parameters[i, 8]) + ' dex') print(' > elapsed time for one MaStar %.2f s' % (clock() - start_time)) ##### making a preliminary output file ##### print(' > making a preliminary output file') f = open(output_dir + 'prelim_output/' + mastarid[i] + '_she-ra', 'w') f.write('%r %r %r %i %i %i %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n' \ %(mangaid[i], mastarid[i], starid[i], \ plate[i], ifudesign[i], mjd[i], \ ra[i],dec[i], \ parameters[i,0],parameters[i,1],parameters[i,2], \ parameters[i,3],parameters[i,4],parameters[i,5], \ parameters[i,6],parameters[i,7],parameters[i,8], \ ppxf_info[i,0], ppxf_info[i,1], ppxf_info[i,2], \ mags[i,0], mags[i,1], mags[i,2], mags[i,3], mags[i,4], \ indices[i,0], indices[i,1], indices[i,2], indices[i,3], indices[i,4], \ indices[i,5], indices[i,6], indices[i,7], indices[i,8], indices[i,9], \ indices[i,10], indices[i,11], indices[i,12], indices[i,13], indices[i,14], \ indices[i,15], indices[i,16], indices[i,17], indices[i,18], indices[i,19], \ indices[i,20], indices[i,21], indices[i,22], indices[i,23], indices[i,24], \ indices[i,25], indices[i,26], indices[i,27], indices[i,28], indices[i,29], \ indices[i,30], indices[i,31], indices[i,32], indices[i,33], indices[i,34], \ indices[i,35], indices[i,36], indices[i,37], indices[i,38], indices[i,39], \ indices[i,40], indices[i,41])) f.close() print(' > spectrum ' + str(i) + ' done') #sys.stdout.write(" > spectrum = %d %s \r" % (i+1,'done')) #sys.stdout.flush() print(' > all spectra done') print(' > elapsed time for all MaStar %.2f s' % (clock() - full_time))
def find_best_template(wl_obs, flux, err, hdr, spectral_library): import numpy as np import matplotlib.pyplot as plt from scipy.interpolate import splrep,splev from time import clock t2 = clock() # Read a spectrum and define the wavelength range obs_spectrum = flux obs_spectrum_header = hdr obs_error_spectrum = err obs_lambda_range = np.array([min(wl), max(wl)]) z = 0.0 # Initial estimate of the galaxy redshift obs_lambda_range = obs_lambda_range/(1+z) # Compute approximate restframe wavelength range #Get median of positive values m = np.median(obs_error_spectrum[obs_error_spectrum > 0]) # Assign the median to the negative elements obs_error_spectrum[obs_error_spectrum <= 0] = m #logarithmically rebin while conserving flux tell_obs, obs_lambda, velscale = util.log_rebin(obs_lambda_range, obs_spectrum) tell_obs_err, obs_lambda, velscale = util.log_rebin(obs_lambda_range, obs_error_spectrum) #Normalize to avoid numerical issues norm = np.median(tell_obs) tell_obs = tell_obs/norm tell_obs_err = tell_obs_err/norm # Load and prepare Model stellar library # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the target spectrum, to determine # the size needed for the array which will contain the template spectra. hdu = fits.open(spectral_library[0]) library_spectrum = hdu[0].data library_spectrum_header = hdu[0].header wl_lib = np.e**(np.arange((library_spectrum_header['NAXIS1']))*library_spectrum_header['CDELT1']+library_spectrum_header['CRVAL1']) #Make empty template holder f = splrep(wl_lib,library_spectrum, k=1) hdu_wave_short = splev(wl_obs,f) lib_lambda_range = np.array([min(wl_obs),max(wl_obs)]) tell_lib, lib_lambda, velscale = util.log_rebin(lib_lambda_range, hdu_wave_short, velscale=velscale) templates = np.empty((tell_lib.size,len(spectral_library))) # Convolve the whole library of spectral templates # with the quadratic difference between the target and the # library instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. for j in range(len(spectral_library)): t = clock() hdu = fits.open(spectral_library[j]) library_spectrum = hdu[0].data # Interpolate template spectrum to match input spectrum f = splrep(wl_lib,library_spectrum,k=1) interpolated_library_spectrum = splev(wl_obs,f) # Logarithmically rebin template spectra tell_lib, lib_lambda, velscale = util.log_rebin(lib_lambda_range,interpolated_library_spectrum, velscale=velscale) templates[:,j] = tell_lib/np.median(tell_lib) # Normalizes templates if j % 60 == 0: print 'Approximated remaining time (s) for setup of template spectra: '+ str(len(spectral_library)*(clock() - t) - j*(clock() - t)) + 's' #Excluding areas of strong telluric absorbtion from fitting if obs_spectrum_header['HIERARCH ESO SEQ ARM'] == "UVB": mask = (obs_lambda < np.log(5500)) & (obs_lambda > np.log(3100)) goodPixels = np.where(mask == True)[0] elif obs_spectrum_header['HIERARCH ESO SEQ ARM'] == "VIS": mask = (obs_lambda > np.log(5500)) & (obs_lambda < np.log(6350)) | (obs_lambda > np.log(6380)) & (obs_lambda < np.log(6860)) | (obs_lambda > np.log(7045)) & (obs_lambda < np.log(7140)) | (obs_lambda > np.log(7355)) & (obs_lambda < np.log(7570)) | (obs_lambda > np.log(7710)) & (obs_lambda < np.log(8090)) | (obs_lambda > np.log(8400)) & (obs_lambda < np.log(8900)) | (obs_lambda > np.log(9900)) & (obs_lambda < np.log(10100)) goodPixels = np.where(mask == True)[0] elif obs_spectrum_header['HIERARCH ESO SEQ ARM'] == "NIR": mask = (obs_lambda > np.log(10000)) & (obs_lambda < np.log(10950)) | (obs_lambda > np.log(12240)) & (obs_lambda < np.log(12500)) | (obs_lambda > np.log(12800)) & (obs_lambda < np.log(12950)) | (obs_lambda > np.log(15300)) & (obs_lambda < np.log(17100)) | (obs_lambda > np.log(21000)) & (obs_lambda < np.log(23700)) goodPixels = np.where(mask == True)[0] # Initial parameters for LOSVD c = 299792.458 dv = 0# (logLam2[0]-logLam1[0])*c # km/s vel = 0 # Initial estimate of the LOSVD km/s start = [vel, 10.] # (km/s), starting guess for [V,sigma] # Here the actual fit starts. pp = ppxf.ppxf(templates, tell_obs, tell_obs_err, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=5, mdegree=5, vsyst=dv, regul = 10) print "Formal errors:" print " dV dsigma dh3 dh4" print "".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2)) print 'elapsed time (s) for ppxf: ', clock() - t2 print 'Best-fitting redshift z:', (z + 1)*(1 + pp.sol/c) - 1 print 'Best-fitting error on redshift z:',((z + 1)*(1 + pp.sol/c) - 1) - ((z + 1)*(1 + pp.error*np.sqrt(pp.chi2)/c) - 1) print 'Rebinning to linear axes' import spec obj_spec = spec.resamplespec(wl_obs,np.e**obs_lambda,pp.galaxy, oversamp = 1000) template_fit = spec.resamplespec(wl_obs,np.e**obs_lambda,pp.bestfit, oversamp =1000) # plt.plot(wl_obs,obj_spec, color='black', linestyle = 'steps-mid') # plt.plot(wl_obs, template_fit, color='red') return obj_spec,template_fit,obs_spectrum_header
def mcmc(galaxy, z=0.01, vel=0.0, sig=200.0, discard=2, set_range=[4200,10000]): print ' MCMC' results = np.zeros((2,repeats)) for i in range(2): templates, bin_log, noise, velscale, start, goodpixels, moments, degree, dv, \ lambdaq, plot, quiet = setup(galaxy, z=z, vel=vel, sig=sig, discard=discard, set_range=set_range) pp = ppxf(templates, bin_log, noise, velscale, start, goodpixels=goodpixels, moments=moments, degree=degree, vsyst=dv, lam=lambdaq, plot=plot, quiet=quiet) z = (z + 1)*np.sqrt((1 + pp.sol[0]/c)/(1 - pp.sol[0]/c)) - 1 templates, bin_log, noise, velscale, start, goodpixels, moments, \ degree, dv, lambdaq, plot, quiet = setup(galaxy, z=z, vel=vel, sig=sig, discard=discard, set_range=set_range) chi = 1000000 for i in range(repeats): v_sav = vel sigma_sav = sig chi_sav = chi if not quiet: print 'i=',i print "input v: ",v_sav print "input sig: ",sigma_sav print "input chi2: ",chi_sav start = [vel,sig] if i != repeats-1: pp = ppxf(templates, bin_log, noise, velscale, start, goodpixels=goodpixels, moments=moments, degree=degree, vsyst=dv, lam=lambdaq, plot=not quiet, quiet=quiet) else: pp = ppxf(templates, bin_log, noise, velscale, start, goodpixels=goodpixels, moments=moments, degree=degree, vsyst=dv, lam=lambdaq, plot=False, quiet=quiet) vel = pp.sol[0] sig = pp.sol[1] chi = pp.chi2 results[:,i] = [vel,sig] if abs(chi) > abs(chi_sav) or np.random.uniform() > threshold: vel = v_sav + np.random.uniform(low=-1, high=1)*local_step sig = sigma_sav + np.random.uniform(low=-1, high=1)*local_step chi = chi_sav vel = np.mean(results[0,:]) sig = np.mean(results[1,:]) if not quiet: print "Mean vel: :",vel print "MEAN vel dispersion: ",sig # ----------===============================================--------- # ----------================= Save Result =================--------- # ----------===============================================--------- dir = '%s/Data/vimos' %(cc.base_dir) fig, ax = plt.subplots() ax.scatter(results[0,:],results[1,:]) ax.scatter(vel,sig, marker='*') ax.set_xlabel("velocity") ax.set_ylabel("velocity dispersion") ax.set_title("MCMC for initial conditions") fig.savefig('%s/analysis/%s/MCMC_initial_fit.png' %(dir,galaxy)) if plot: plt.show() data_file = "%s/analysis/galaxies.txt" % (dir) d = np.loadtxt(data_file, unpack=True, dtype=str) galaxy_gals = d[0][1:] z_gals, vel_gals, sig_gals = d[1][1:].astype(float), d[2][1:].astype(float), \ d[3][1:].astype(float), x_gals, y_gals = d[4][1:].astype(int), d[5][1:].astype(int) SN_gals = {d[i][0]:d[i][1:].astype(float) for i in range(6,len(d))} # If galaxy is already in galaxies.txt file try: i_gal = np.where(galaxy_gals == galaxy) except: i_gal = -1 galaxy_gals = [galaxy_gals, galaxy] z_gals = [z_gals, z] vel_gals = [vel_gals, vel] sig_gals = [sig_gals, sig] else: z_gals[i_gal] = z vel_gals[i_gal] = vel sig_gals[i_gal] = sig temp = "{0:12}{1:11}{2:10}{3:15}{4:4}{5:4}" + ''.join(['{%i:%i}'%(i+6,len(t)+1) for i, t in enumerate(SN_gals.keys())]) + "\n" SN_titles = list(SN_gals.keys()) with open(data_file, 'w') as f: f.write(temp.format("Galaxy", "z", "velocity", "sigma", "x", "y", *(s for s in SN_titles))) for i in range(len(galaxy_gals)): f.write(temp.format(galaxy_gals[i], str(round(z_gals[i],7)), str(round(vel_gals[i],4)), str(round(sig_gals[i],4)), str(int(x_gals[i])), str(int(y_gals[i])), *(str(round(SN_gals[s][i],2)) for s in SN_titles)))
def ppxf_example_population_gas_sdss(): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = file_dir + '/spectra/NGC3522_SDSS_DR8.fits' hdu = fits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t['wavelength'] > 3540) & (t['wavelength'] < 7409) flux = t['flux'][mask] galaxy = flux/np.median(flux) # Normalize spectrum to avoid numerical issues wave = t['wavelength'][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 = file_dir + '/miles_models/Mun1.30*.fits' miles = lib.miles(pathname, velscale, FWHM_gal) # The stellar templates are reshaped 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) # 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. # c = 299792.458 dv = c*(miles.log_lam_temp[0] - np.log(wave[0])) # km/s # Here the actual fit starts. The best fit is plotted on the screen. # # 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. # vel = c*np.log(1 + z) # eq.(8) of Cappellari (2017) start = [vel, 180.] # (km/s), starting guess for [V, sigma] t = clock() n_temps = stars_templates.shape[1] n_balmer = 4 # Number of Balmer lines included in the fit n_forbidden = 5 # 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 components moments = [4, 2, 2] # Adopt the same starting value for the stars and the two gas components start = [start, start, start] 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) # 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. # For every kinematic component, specify whether they are # gas emission, using the boolean `gas_component` keyword. 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)
def fitms(spectrum,error,template_list, out_prefix, cut=0.75, pre=0, mdegree=25, degree=4): pd = PDF(out_prefix+'.pdf') #make the galaxy wavelength vector data_hdu = pyfits.open(spectrum) error_hdu = pyfits.open(error) ddw = data_hdu[0].header['CDELT1'] lambda0 = data_hdu[0].header['CRVAL1'] wave = lambda0+ \ np.arange(data_hdu[0].shape[1])*ddw # wave = wave[pre:] lamRange1 = np.array([wave.min(),wave.max()])#/1.008246 galaxy = data_hdu[0].data[0]#[pre:] loggalaxy, logLam1, velscale = pputil.log_rebin(lamRange1,galaxy) masklow = wave.min() - 500. maskhigh = wave.max() + 500. print data_hdu[0].shape print masklow,maskhigh c = 299792.458 FWHM_gal = 0.95/1.008246 #AA FWHM_tmp = 0.55 #AA FWHM_diff = np.sqrt(FWHM_gal**2 - FWHM_tmp**2) sigma = FWHM_diff/2.355/ddw temp_hdu = pyfits.open(template_list[0]) twave = temp_hdu[0].header['CRVAL1'] + \ np.arange(temp_hdu[0].data.shape[1])*temp_hdu[0].header['CDELT1'] mask = np.where((twave > masklow) & (twave < maskhigh)) template = temp_hdu[0].data[0][mask] twave = twave[mask] lamRange2 = np.array([twave.min(),twave.max()]) template = ndimage.gaussian_filter1d(template,sigma) logtmp, logLam2, velscale_tmp = pputil.log_rebin(lamRange2, template, velscale=velscale) bestfits = np.empty((1,wave.size)) templates = np.empty((logtmp.size)) print templates.shape for template_file in template_list: temp_hdu = pyfits.open(template_file)[0] data = temp_hdu.data header = temp_hdu.header twave = header['CRVAL1'] + np.arange(data.shape[1])*header['CDELT1'] mask = (twave > masklow) & (twave < maskhigh) twave = twave[mask] for i in range(data.shape[0]): td = data[i][mask] # tfit = ADE.polyclip(twave,td,order) # td /= tfit(twave) td = ndimage.gaussian_filter1d(td,sigma) logtd, logLam2, velscale = pputil.log_rebin(lamRange2,td, velscale=velscale) templates = np.vstack((templates,logtd/np.median(logtd))) templates = templates[1:].T print templates.shape, twave.shape for i in range(data_hdu[0].data.shape[0]): fig = plt.figure() galaxy = data_hdu[0].data[i]#[pre:] noise = error_hdu[0].data[i] # datafit = ADE.polyclip(wave,galaxy,order) # ax = fig.add_subplot(212) # ax.plot(wave,galaxy) # ax.plot(np.arange(templates[:,0].size),templates[:,0]) # ax.set_ylim(0,2) # ax.plot(wave,datafit(wave)) # galaxy /= datafit(wave) loggalaxy, logLam1, velscale = pputil.log_rebin(lamRange1,galaxy) logerror, _, _ = pputil.log_rebin(lamRange1,noise) # pyfits.PrimaryHDU(templates).writeto('templates.fits') print 'velscale:', velscale dv = (logLam2[0] - logLam1[0])*c print 'dv:', dv vel = c*1.008246 start = [0.,2.] loggalaxy /= np.median(loggalaxy) logerror /= np.median(loggalaxy) goodfit = ADE.polyclip(np.arange(loggalaxy.size),loggalaxy,4) tmpgood = np.where(np.abs(loggalaxy - goodfit(np.arange(loggalaxy.size))) < \ cut*np.std(loggalaxy))[0] tmpgood = tmpgood[tmpgood > pre] goodsmooth = np.zeros(loggalaxy.size) goodsmooth[tmpgood] = 1. goodsmooth = ndimage.gaussian_filter1d(goodsmooth,11) goodpixels = np.where(goodsmooth > 0.9)[0] pp = ppxf(templates,loggalaxy, logerror, velscale,start,bias=None,degree=degree,mdegree=mdegree, vsyst=dv,plot=True, goodpixels=goodpixels) print bestfits.shape bestfits = np.vstack((bestfits,pp.bestfit)) ###PLOT### plot_px = np.exp(logLam1) plot_gal = ndimage.gaussian_filter1d(loggalaxy,3) collection = collections.BrokenBarHCollection.span_where( plot_px, ymin=0,ymax=4, where=goodsmooth < 0.9, facecolor='red',alpha=0.5) collection2 = collections.BrokenBarHCollection.span_where( plot_px, ymin=0,ymax=4, where=np.arange(loggalaxy.size) <= pre, facecolor='red',alpha=0.5) ax2 = fig.add_subplot(411) ax2.plot(np.exp(logLam1),plot_gal) ax2.plot(np.exp(logLam1),goodfit(np.arange(plot_px.size))) ax2.add_collection(collection) ax2.add_collection(collection2) ax2.plot(plot_px,pp.bestfit) ax2.set_xlim(plot_px[0],plot_px[-1]) ax5 = ax2.twiny() ax5.set_xlim(0,loggalaxy.size) ax2.set_ylim(0.6,1.4) bbox2 = ax2.get_position().get_points().flatten() plt.setp(ax2.get_xticklabels(),visible=False) ax3 = fig.add_subplot(412,sharex=ax2) bbox3 = ax3.get_position().get_points().flatten() newpos = [bbox3[0], bbox2[1] - (bbox3[3] - bbox3[1]), bbox3[2] - bbox3[0], bbox3[3] - bbox3[1]] ax3.set_position(newpos) ax3.plot(plot_px,loggalaxy - pp.bestfit,'r',lw=0.7) ax3.set_ylim(-0.2,0.2) ax3.set_xlim(plot_px[0],plot_px[-1]) ax = fig.add_subplot(212) pidx = np.arange(1525,1725,1) ax.plot(plot_px[pidx],plot_gal[pidx]) ax.plot(plot_px[pidx],pp.bestfit[pidx]) ax4 = ax.twiny() velx = np.arange(pidx.size)*velscale velx -= velx[-1]/2. ax4.set_xlim(velx[0],velx[-1]) pd.savefig(fig) plt.close(fig) bestfits = bestfits[1:] pd.close() bfh = pyfits.PrimaryHDU(np.array(bestfits,dtype=np.float32)) bfh.header.update('CDELT1',ddw) bfh.header.update('CRVAL1',wave.min()) bfh.header.update('CRPIX1',1) bfh.header.update('CTYPE1','LINEAR') bfh.writeto(out_prefix+'.ms.fits',clobber=True) iraf.noao.onedspec.dispcor(out_prefix+'.ms.fits',out_prefix+'_lin.ms.fits', linearize=True,log=False,flux=False, dw=ddw,w1=wave.min(),w2=wave.max(),nw='INDEF', samedis=True) plt.close('all') return
def run(self): if self.params.start is None: if not self.params.narrow_broad: start = [[self.vel, self.sig]] * (max(self.component) + 1) else: start = [[self.vel, self.sig]] * (max(self.component) / 2 + 1) # Start narrow line component at half of broad line start.extend([[self.vel, self.sig / 2]] * (max(self.component) / 2)) else: start = self.params.start for i, e in enumerate(self.element): if start[i][0] is None: start[i][0] = self.vel if start[i][1] is None: if 'n_' not in e: start[i][1] = self.sig else: start[i][1] = self.sig / 2. moments = [self.params.stellar_moments] + [self.params.gas_moments] * \ max(self.component) ## ----------============== The bestfit part ===============--------- ppxf.__init__(self, self.templates, self.bin_log, self.bin_log_noise, self.velscale, start, goodpixels=self.goodPixels, mdegree=self.params.mdegree, moments=moments, degree=self.params.degree, vsyst=self.dv, component=self.component, lam=self.lambdaq, plot=not self.params.quiet, quiet=self.params.quiet, produce_plot=False) if self.params.gas == 0: self.sol = [self.sol] self.error = [self.error] ## ----------===============================================--------- ## ----------================= The MC part =================--------- ## ----------===============================================--------- if self.params.use_all_temp is not None: self.MCstellar_kin = np.zeros( (self.params.reps, abs(self.params.stellar_moments))) self.MCstellar_kin_err = np.zeros( (self.params.reps, abs(self.params.stellar_moments))) self.MCbestfit_uncert = np.zeros((3, len(self.galaxy))) MCbestfit_mean = np.zeros(len(self.galaxy)) if self.params.gas: self.MCgas_kin = np.zeros((max(self.component), self.params.reps, abs(self.params.gas_moments))) self.MCgas_kin_err = np.zeros( (max(self.component), self.params.reps, abs(self.params.gas_moments))) n_lines = len(self.e_templates.templatesToUse) # self.MCgas_weights = np.zeros((n_lines, self.params.reps)) self.MCgas_uncert_spec = np.zeros((n_lines, 3, len(self.galaxy))) MCgas_mean_spec = np.zeros((n_lines, len(self.galaxy))) if self.params.reps == 0: self.MCgas_uncert_spec = np.zeros((n_lines, len(self.galaxy))) else: self.MCgas_kin = None self.MCgas_kin_err = None # self.MCgas_weights = None for rep in range(self.params.reps): random = np.random.randn(len(self.bin_log_noise)) if self.params.use_residuals and rep == 0: _, residuals, _ = moving_weighted_average(self.lam, self.bestfit - self.bin_lin, step_size=3., interp=True) add_noise = random * np.sqrt(self.bin_log_noise**2 + residuals**2) elif self.params.use_residuals: _, residuals, _ = moving_weighted_average(self.lam, ppMC.bestfit - self.bin_lin, step_size=3., interp=True) add_noise = random * np.sqrt(self.bin_log_noise**2 + residuals**2) else: add_noise = random * np.abs(self.bin_log_noise) self.bin_log = self.bestfit + add_noise ppMC = ppxf(self.templates, self.bin_log, self.bin_log_noise, self.velscale, start, goodpixels=self.goodPixels, moments=moments, degree=self.params.degree, vsyst=self.dv, lam=self.lambdaq, plot=not self.params.quiet, quiet=self.params.quiet, bias=0.1, component=self.component, mdegree=self.params.mdegree) if self.params.gas == 0: ppMC.sol = [ppMC.sol] ppMC.error = [ppMC.error] if self.params.use_all_temp is not None: self.MCstellar_kin[ rep, :] = ppMC.sol[0][0:abs(self.params.stellar_moments)] self.MCstellar_kin_err[ rep, :] = ppMC.error[0][0:abs(self.params.stellar_moments)] # Find uncertainty in bestfit new_mean_bestfit_spec = ( (rep + 1) * MCbestfit_mean + ppMC.bestfit) / (rep + 2) if rep < 3: # Save spec until 3 reps have been completed self.MCbestfit_uncert[rep, :] = ppMC.bestfit else: # Finding sigma_N from x_N, mean_N, mean_(N-1) and sigma_(N-1) # NB: rep = N-1 due to being zero-based self.MCbestfit_uncert = np.sqrt( ((ppMC.bestfit - new_mean_bestfit_spec) * (ppMC.bestfit - MCbestfit_mean) + rep * self.MCbestfit_uncert**2) / (rep + 1)) MCbestfit_mean = np.array(new_mean_bestfit_spec) if rep == 2: # Calc std at 3rd rep self.MCbestfit_uncert = np.std(self.MCbestfit_uncert, axis=0) # Gas kinematics from all reps for g in range(len(self.element) - 1): self.MCgas_kin[g, rep, :] = ppMC.sol[ g + 1][0:abs(self.params.gas_moments)] self.MCgas_kin_err[g, rep, :] = ppMC.error[ g + 1][0:abs(self.params.gas_moments)] # Find uncertainty in fitted emission lines for i, n in enumerate(self.e_templates.templatesToUse): e_line_spec = ppMC.matrix[:, -n_lines + i] * ppMC.weights[-n_lines + i] new_mean_gas_spec = ((rep + 1) * MCgas_mean_spec[i, :] + e_line_spec) / (rep + 2) if rep < 3 or self.params.reps == 0: self.MCgas_uncert_spec[i, rep, :] = e_line_spec else: self.MCgas_uncert_spec[i, :] = np.sqrt( ((e_line_spec - new_mean_gas_spec) * (e_line_spec - MCgas_mean_spec[i, :]) + rep * self.MCgas_uncert_spec[i, :]**2) / (rep + 1)) MCgas_mean_spec[i, :] = np.array(new_mean_gas_spec) if rep == 2 and self.params.gas != 0: self.MCgas_uncert_spec = np.std(self.MCgas_uncert_spec, axis=1) if self.params.produce_plot: self.fig, self.ax = create_plot(self).produce
def ppxf_simulation_example(): hdu = fits.open('miles_models/Mun1.30Zp0.00T12.5893.fits' ) # Solar metallicitly, Age=12.59 Gyr ssp = hdu[0].data h = hdu[0].header lamRange = h['CRVAL1'] + np.array([0., h['CDELT1'] * (h['NAXIS1'] - 1)]) c = 299792.458 # speed of light in km/s velscale = c * h['CDELT1'] / max( lamRange) # Do not degrade original velocity sampling star, logLam, velscale = util.log_rebin(lamRange, ssp, velscale=velscale) # The finite sampling of the observed spectrum is modeled in detail: # the galaxy spectrum is obtained by oversampling the actual observed spectrum # to a high resolution. This represent the true spectrum, which is later resampled # to lower resolution to simulate the observations on the CCD. Similarly, the # convolution with a well-sampled LOSVD is done on the high-resolution spectrum, # and later resampled to the observed resolution before fitting with PPXF. factor = 10 # Oversampling integer factor for an accurate convolution starNew = ndimage.interpolation.zoom( star, factor, order=3) # This is the underlying spectrum, known at high resolution star = rebin( starNew, factor ) # Make sure that the observed spectrum is the integral over the pixels np.random.seed(133) # for reproducible results h3 = 0.1 # Adopted G-H parameters of the LOSVD h4 = -0.1 sn = 30. # Adopted S/N of the Monte Carlo simulation m = 300 # Number of realizations of the simulation moments = 4 velV = np.random.rand(m) # velocity in *pixels* [=V(km/s)/velScale] sigmaV = np.linspace( 0.5, 4, m) # Range of sigma in *pixels* [=sigma(km/s)/velScale] result = np.zeros((m, moments)) # This will store the results t = clock() for j, (vel, sigma) in enumerate(zip(velV, sigmaV)): dx = int( abs(vel) + 4.0 * sigma) # Sample the Gaussian and GH at least to vel+4*sigma x = np.linspace( -dx, dx, 2 * dx * factor + 1) # Evaluate the Gaussian using steps of 1/factor pixels. w = (x - vel) / sigma w2 = w**2 gauss = np.exp(-0.5 * w2) gauss /= np.sum(gauss) # Normalized total(gauss)=1 h3poly = w * (2. * w2 - 3.) / np.sqrt(3.) # H3(y) h4poly = (w2 * (4. * w2 - 12.) + 3.) / np.sqrt(24.) # H4(y) losvd = gauss * (1. + h3 * h3poly + h4 * h4poly) galaxy = signal.fftconvolve( starNew, losvd, mode="same") # Convolve the oversampled spectrum galaxy = rebin( galaxy, factor) # Integrate spectrum into original spectral pixels noise = galaxy / sn # 1sigma error spectrum galaxy = np.random.normal(galaxy, noise) # Add noise to the galaxy spectrum start = np.array([ vel + np.random.random(), sigma * np.random.uniform(0.85, 1.15), 0, 0 ]) * velscale # Convert to km/s pp = ppxf(star, galaxy, noise, velscale, start, goodpixels=np.arange(dx, galaxy.size - dx), plot=False, moments=moments, bias=0.3, oversample=None) result[j, :] = pp.sol print('Calculation time: %.2f s' % (clock() - t)) plt.clf() plt.subplot(221) plt.plot(sigmaV * velscale, (result[:, 0] / velscale - velV) / sigmaV, '+k') plt.axhline(0, color='r') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-0.3, 0.3) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel(r'$(V - V_{\rm in})/\sigma_{\rm in}$') plt.text(2.05 * velscale, -0.2, r'2$\times$velscale') plt.subplot(222) plt.plot(sigmaV * velscale, np.log10(result[:, 1] / (velscale * sigmaV)), '+k') plt.axhline(0, color='r') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-0.15, 0.15) plt.xlabel(r'$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel(r'$\log(\sigma/\sigma_{\rm in})$') plt.text(2.05 * velscale, -0.1, r'2$\times$velscale') plt.subplot(223) plt.plot(sigmaV * velscale, result[:, 2], '+k') plt.axhline(h3, color='r') plt.axhline(0, linestyle='dotted', color='limegreen') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-0.15 + h3, 0.15 + h3) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel('$h_3$') plt.text(2.05 * velscale, h3 - 0.1, r'2$\times$velscale') plt.subplot(224) plt.plot(sigmaV * velscale, result[:, 3], '+k') plt.axhline(h4, color='r') plt.axhline(0, linestyle='dotted', color='limegreen') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-0.15 + h4, 0.15 + h4) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel('$h_4$') plt.text(2.05 * velscale, h4 - 0.1, r'2$\times$velscale') plt.tight_layout() plt.pause(0.01)
def ppxf_kinematics_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS.fits' hdu = pyfits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t.field('wavelength') > 3540) & (t.field('wavelength') < 7409) galaxy = t[mask].field('flux')/np.median(t[mask].field('flux')) # Normalize spectrum to avoid numerical issues wave = t[mask].field('wavelength') noise = galaxy*0 + 0.0156 # 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 = np.log(wave[1]/wave[0])*c FWHM_gal = 2.76 # SDSS has an instrumental resolution FWHM of 2.76A. # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # wave = wave/(1+z) # Compute approximate restframe wavelength # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission # vazdekis = glob.glob('miles_models/Mun1.30Z*.fits') FWHM_tem = 2.51 # Vazdekis+10 spectra have a resolution FWHM of 2.51A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SDSS galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = pyfits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array([0.,h2['CDELT1']*(h2['NAXIS1']-1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size,len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SDSS and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SDSS # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels for j in range(len(vazdekis)): hdu = pyfits.open(vazdekis[j]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp,sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:,j] = sspNew/np.median(sspNew) # Normalizes 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 (see above). # c = 299792.458 dv = (logLam2[0]-np.log(wave[0]))*c # km/s vel = c*z # Initial estimate of the galaxy velocity in km/s goodpixels = util.determine_goodpixels(np.log(wave),lamRange2,vel) # 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. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=10, vsyst=dv, clean=False) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
def run_ppxf(spectra, velscale, ncomp=2, has_emission=True, mdegree=-1, degree=20, pkls=None, plot=False, data_sky=None): """ Run pPXF in a list of spectra""" if isinstance(spectra, str): spectra = [spectra] if isinstance(pkls, str): pkls = [pkls] if pkls == None: pkls = [x.replace(".fits", ".pkl") for x in spectra] ########################################################################## # Load templates for both stars and gas star_templates, logLam2, delta, miles= stellar_templates(velscale) gas_templates,logLam_gas, delta_gas, gas_files=emission_templates(velscale) ########################################################################## # Join templates in case emission lines are used. if has_emission: templates = np.column_stack((star_templates, gas_templates)) else: templates = star_templates ########################################################################## if ncomp == 1: components = 0 moments = [4] templates_names = miles elif ncomp == 2: components = np.hstack((np.zeros(len(star_templates[0])), np.ones(len(gas_templates[0])))) moments = [4,2] templates_names = np.hstack((miles, gas_files)) else: raise Exception("ncomp has to be 1 or 2.") for i, spec in enumerate(spectra): print "pPXF run of spectrum {0} ({1} of {2})".format(spec, i+1, len(spectra)) pkl = pkls[i] ###################################################################### # Read one galaxy spectrum and define the wavelength range specfile = os.path.join(data_dir, spec) hdu = pf.open(specfile) spec_lin = hdu[0].data h1 = pf.getheader(specfile) lamRange1 = h1['CRVAL1'] + np.array([0.,h1['CDELT1']*(h1['NAXIS1']-1)]) ###################################################################### # Degrade observed spectra to match template resolution FWHM_dif = np.sqrt(FWHM_tem**2 - FWHM_spec**2) sigma = FWHM_dif/2.355/delta # Sigma difference in pixels spec_lin = ndimage.gaussian_filter1d(spec_lin,sigma) ###################################################################### # Rebin to log scale galaxy, logLam1, velscale = util.log_rebin(lamRange1, spec_lin, velscale=velscale) ###################################################################### # First guess for the noise noise = np.ones_like(galaxy) * np.std(galaxy - medfilt(galaxy, 5)) ###################################################################### # Calculate difference of velocity between spectrum and templates # due to different initial wavelength dv = (logLam2[0]-logLam1[0])*c ###################################################################### # Set first guess from setup files start, goodPixels = read_setup_file(spec, logLam1, mask_emline=False) ###################################################################### # Expand start variable to include multiple components if ncomp > 1: start = [start, [start[0], 30]] ###################################################################### # Read sky in needed if data_sky == None: sky = None else: sky_lin = pf.getdata(data_sky[i]) sky_lin = ndimage.gaussian_filter1d(sky_lin,sigma) sky, logLam1, velscale = util.log_rebin(lamRange1, sky_lin, velscale=velscale) sky = sky.reshape(-1,1) ###################################################################### # First pPXF interaction if os.path.exists(spec.replace(".fits", ".pkl")): pp0 = pPXF(spec, velscale, pklfile=spec.replace(".fits", ".pkl")) noise0 = pp0.noise else: pp0 = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=False, moments=moments, degree=12, mdegree=-1, vsyst=dv, component=components, sky=sky) rms0 = galaxy[goodPixels] - pp0.bestfit[goodPixels] noise0 = 1.4826 * np.median(np.abs(rms0 - np.median(rms0))) noise0 = np.zeros_like(galaxy) + noise0 # Second pPXF interaction, realistic noise estimation pp = ppxf(templates, galaxy, noise0, velscale, start, goodpixels=goodPixels, plot=plot, moments=moments, degree=degree, mdegree=mdegree, vsyst=dv, component=components, sky=sky) # pp.template_files = templates_names # pp.has_emission = has_emission ###################################################################### # Save to output file to keep session with open(pkl, "w") as f: pickle.dump(pp, f) ###################################################################### return
def ppxf_run_MaNGA_galaxy(ifu, fname_tem, first_few=None, Tsample=4, over=True): plt.close('all') plate = ifu[0] ifudsgn = ifu[1] fname_ifu = 'manga-{}-{}-LOGCUBE.fits.gz'.format(plate, ifudsgn) # now read in drpall drpall = table.Table.read(m.drpall_loc + 'drpall-v1_3_3.fits', format='fits') objconds = drpall['plateifu'] == '{}-{}'.format(plate, ifudsgn) obj = drpall[objconds] c = 299792.458 z = obj['nsa_redshift'] if z == -9999.: z = 0.1 # now read in IFU ifu = fits.open(fname_ifu) ifu_flux = ifu['FLUX'].data ifu_ivar = ifu['ivar'].data ifu_mask = ifu['MASK'].data L_ifu = ifu['WAVE'].data red_scattered_light = L_ifu > 9500. logL_ifu = np.log(L_ifu) - z L_ifu = np.exp(logL_ifu) res_path = '{}-{}/'.format(plate, ifudsgn) try: os.makedirs(res_path) except OSError: pass # read in stellar templates # templates have already been convolved to the proper resolution tems = fits.open(fname_tem) htems = tems[0].header dtems = tems[0].data[::Tsample, :, :] dtems_red = kin_models(tems) print dtems_red.shape nT, nZ, nL = dtems.shape Zs = htems['CRVAL2'] + np.linspace(0., htems['CDELT2'] * (htems['NAXIS2'] - 1), htems['NAXIS2']) Ts = htems['CRVAL3'] + np.linspace(0., htems['CDELT3'] * (htems['NAXIS3'] - 1), htems['NAXIS3']) logL_tem = np.linspace(htems['CRVAL1'], htems['CRVAL1'] + (nL - 1) * htems['CDELT1'], nL) # base e L_tem = np.exp(logL_tem) dtems_med = np.median(dtems) dtems /= dtems_med ssps = np.swapaxes(dtems, 0, 2) ssps = np.reshape(ssps, (nL, -1)) ssps_red = np.swapaxes(dtems_red, 0, 2) ssps_red = np.reshape(ssps_red, (nL, -1)) vel = z * c veldisp = obj['nsa_vdisp'] if veldisp < 0.: # deal with non-measured veldisps veldisp = 300. velscale = (logL_ifu[1] - logL_ifu[0]) * c dl = L_tem[0] - L_ifu[0] dv = c * (logL_tem[0] - logL_ifu[0]) # km/s moments = 4 regul_err = .01 ebvgal = obj['ebvgal'] start = [0., veldisp] reg_dim = [nZ, nT] spaxels = which_spaxels(fname_ifu) n_stellar_tems = nZ * nT moments0 = [ 2, ] moments1 = [ -2, ] start = [0., veldisp] i = 0 for spaxel in spaxels: gridx, gridy = spaxel['gridx'], spaxel['gridy'] print 'Spaxel row {}; col {}'.format(gridx, gridy) figpath = res_path + '{}-{}_pp_fit.png'.format(gridx, gridy) fig_ex = os.path.exists(figpath) if (spaxel['good'] == True) and ((fig_ex == False) or (over == True)): goodpixels = 1 - (ifu_mask[:, gridy, gridx] & 10) goodpixels *= (1 - (ifu_mask[:, gridy, gridx] & 8)) goodpixels *= (np.isfinite(ifu_ivar[:, gridy, gridx])) # red end has some scattered light, so throw it out goodpixels *= (1 - red_scattered_light) emlines = line_mask(logL_ifu, 800.) goodpixels *= emlines goodpixels_i = np.where(goodpixels)[0] galaxy = ifu_flux[:, gridy, gridx] ivar = ifu_ivar[:, gridy, gridx] noise = 1. / np.sqrt(ivar) noise = np.where(np.isfinite(noise), noise, 9999.) med = np.median(galaxy) try: pp0 = ppxf(templates=ssps_red, galaxy=galaxy / med, noise=noise / med, goodpixels=goodpixels_i, start=start, vsyst=dv, velScale=velscale, moments=moments0, degree=-1, mdegree=-1, clean=False, lam=L_ifu, regul=None) pp = ppxf(templates=ssps, galaxy=galaxy / med, noise=noise / med, goodpixels=goodpixels_i, start=pp0.sol, vsyst=dv, velScale=velscale, moments=moments1, degree=-1, mdegree=-1, reddening=ebvgal, clean=False, lam=L_ifu, regul=1. / regul_err, reg_dim=reg_dim) #print('Desired Delta Chi^2: %.4g' % np.sqrt(2*galaxy.size)) #print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1)*\ # galaxy.size)) except: print '\tFITTING PROBLEM' fit_success = False else: ppxf_fig(pp, (gridx, gridy), tems, (plate, ifudsgn), reg_dim) plt.savefig(figpath, dpi=300) start = pp.sol[:2] pp_res = { 'bestfit': pp.bestfit, 'chi2': pp.chi2, 'error': pp0.error, 'galaxy': pp.galaxy, 'lam': pp.lam, 'sol': pp0.sol, 'vsyst': pp.vsyst, 'noise': pp.noise, 'specres': ifu['SPECRES'] } pk.dump( pp_res, file(res_path + '{}-{}-pp.pickle'.format(gridx, gridy), 'w')) finally: i += 1 if i >= first_few: break
# # logLam2, fl_temp = numpy.genfromtxt(directory + 'wise_template.dat', unpack = True, skiprows = 1) wave_temp = numpy.exp(logLam2) lamRange2 = numpy.array([logLam2[0], logLam2[-1]]) # templates = fl_temp # c = 299792.458 dv = (logLam2[0]-logLam1[0])*c # km/s # vel = 1500. # start = [vel, 200.] # (km/s), starting guess for [V,sigma] # pp = ppxf(templates, galaxy, noise, velscale, start, plot=False, moments=2, degree=4, vsyst=dv) # if moments == 4: pp = ppxf(templates, galaxy, noise, velscale, [pp.sol[0], pp.sol[1]], plot=False, moments=2, degree=4, vsyst=dv) # # rec.append(pp.sol[0]) sig.append(pp.sol[1]) chi2.append(pp.chi2) er.append(pp.error*np.sqrt(pp.chi2)) # # RUN MONTECARLO SIMULATION # lenMC = N_iter_MC #
hdu = fits.open('temp_final_%s.fits' % Z) h_template=hdu[0].header temp_lin=hdu[0].data naxis=h_template['NAXIS1'] lamRange2=h_template['CRVAL1'] + np.array([0.,h_template['CDELT1']*(h_template['NAXIS1']-1.)]) templates, logLam2, velscale= util.log_rebin(lamRange2, temp_lin, velscale=velscale) # Calculate the offset between the template and the galaxy c = 299792.458 dv = (logLam2[0]-logLam1[0])*c noise = galaxy*0 + 1 # Do the ppxf pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, degree=4, moments=2, lam = np.exp(logLam1), vsyst=dv) # Save the template and the weights if make_temp: find_weights = pp.weights > 0 nw = len(find_weights) weights = pp.weights[find_weights] temp_final = np.average(templates_lin[:,find_weights], weights=weights,axis=1) fits.writeto('temp_final_'+Z+'.fits',temp_final, header=h_template, clobber=True) ascii.write(Table([temp_files,pp.weights]), name+'_tempWeights.txt') # Plot fig=plt.figure(figsize=(9,5))
def ppxf_two_components_example(): hdu = fits.open('miles_models/Mun1.30Zp0.00T12.5893.fits') # Solar metallicitly, Age=12.59 Gyr gal_lin = hdu[0].data h1 = hdu[0].header lamRange1 = h1['CRVAL1'] + np.array([0., h1['CDELT1']*(h1['NAXIS1']-1)]) c = 299792.458 # speed of light in km/s velscale = c*h1['CDELT1']/max(lamRange1) # Do not degrade original velocity sampling model1, logLam1, velscale = util.log_rebin(lamRange1, gal_lin, velscale=velscale) model1 /= np.median(model1) hdu = fits.open('miles_models/Mun1.30Zp0.00T01.0000.fits') # Solar metallicitly, Age=1.00 Gyr gal_lin = hdu[0].data model2, logLam1, velscale = util.log_rebin(lamRange1, gal_lin, velscale=velscale) model2 /= np.median(model2) model = np.column_stack([model1, model2]) galaxy = np.empty_like(model) # These are the input values in spectral pixels # for the (V,sigma) of the two kinematic components # vel = np.array([0., 250.])/velscale sigma = np.array([200., 100.])/velscale # The synthetic galaxy model consists of the sum of two # SSP spectra with age of 1Gyr and 13Gyr respectively # with different velocity and dispersion # for j in range(len(vel)): dx = int(abs(vel[j]) + 4.*sigma[j]) # Sample the Gaussian at least to vel+4*sigma v = np.linspace(-dx, dx, 2*dx + 1) losvd = np.exp(-0.5*((v - vel[j])/sigma[j])**2) # Gaussian LOSVD losvd /= np.sum(losvd) # normaize LOSVD galaxy[:, j] = signal.fftconvolve(model[:, j], losvd, mode="same") galaxy[:, j] /= np.median(model[:, j]) galaxy = np.sum(galaxy, axis=1) sn = 100. np.random.seed(2) # Ensure reproducible results noise = galaxy/sn galaxy = np.random.normal(galaxy, noise) # add noise to galaxy # Adopts two templates per kinematic component # templates = np.column_stack([model1, model2, model1, model2]) # Start both kinematic components from the same guess. # With multiple stellar kinematic components # a good starting guess is essential # start = [np.mean(vel)*velscale, np.mean(sigma)*velscale] start = [start, start] goodPixels = np.arange(20, 6000) t = clock() plt.clf() plt.subplot(211) plt.title("Two components pPXF fit") print("+++++++++++++++++++++++++++++++++++++++++++++") pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, degree=4, moments=[2, 2], component=[0, 0, 1, 1]) plt.subplot(212) plt.title("Single component pPXF fit") print("---------------------------------------------") start = start[0] pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, degree=4, moments=2) plt.tight_layout() plt.pause(0.01) print("=============================================") print("Total elapsed time %.2f s" % (clock() - t))
def run_stellar_templates(velscale): """ Run over stellar templates. """ temp_dir = os.path.join(home, "stellar_templates/MILES_FWHM_3.6") standards_dir = os.path.join(home, "data/standards") table = os.path.join(tables_dir, "lick_standards.txt") ids = np.loadtxt(table, usecols=(0,), dtype=str).tolist() star_pars = np.loadtxt(table, usecols=(26,27,28,)) for night in nights: cdir = os.path.join(standards_dir, night) os.chdir(cdir) standards = sorted([x for x in os.listdir(".") if x.endswith(".fits")]) for standard in standards: name = standard.split(".")[0].upper() if name not in ids: continue print standard idx = ids.index(name) T, logg, FeH = star_pars[idx] tempfile= "MILES_Teff{0:.2f}_Logg{1:.2f}_MH{2:.2f}" \ "_linear_FWHM_3.6.fits".format(T, logg, FeH ) os.chdir(temp_dir) template = pf.getdata(tempfile) htemp = pf.getheader(tempfile) wtemp = htemp["CRVAL1"] + htemp["CDELT1"] * \ (np.arange(htemp["NAXIS1"]) + 1 - htemp["CRPIX1"]) os.chdir(cdir) dsigma = np.sqrt((3.7**2 - 3.6**2))/2.335/(wtemp[1]-wtemp[0]) template = broad2hydra(wtemp, template, 3.6) data = pf.getdata(standard) w = wavelength_array(standard) lamRange1 = np.array([w[0], w[-1]]) lamRange2 = np.array([wtemp[0], wtemp[-1]]) # Rebin to log scale star, logLam1, velscale = util.log_rebin(lamRange1, data, velscale=velscale) temp, logLam2, velscale = util.log_rebin(lamRange2, template, velscale=velscale) w = np.exp(logLam1) goodpixels = np.argwhere((w>4700) & (w<6100)).T[0] noise = np.ones_like(star) dv = (logLam2[0]-logLam1[0])*c pp0 = ppxf(temp, star, noise, velscale, [300.,5], plot=False, moments=2, degree=20, mdegree=-1, vsyst=dv, quiet=True, goodpixels=goodpixels) noise = np.ones_like(noise) * np.nanstd(star - pp0.bestfit) pp0 = ppxf(temp, star, noise, velscale, [0.,5], plot=False, moments=2, degree=20, mdegree=-1, vsyst=dv, goodpixels=goodpixels) pp0.w = np.exp(logLam1) pp0.wtemp = np.exp(logLam2) pp0.template_linear = [wtemp, template] pp0.temp = temp pp0.ntemplates = 1 pp0.ngas = 0 pp0.has_emission = False pp0.dv = dv pp0.velscale = velscale pp0.ngas = 0 pp0.nsky = 0 if not os.path.exists("logs"): os.mkdir("logs") ppsave(pp0, "logs/{0}".format(standard.replace(".fits", ""))) pp = ppload("logs/{0}".format(standard.replace(".fits", ""))) pp = pPXF(standard, velscale, pp) pp.plot("logs/{0}".format(standard.replace(".fits", ".png"))) # plt.show() return
def ppxf_kinematics_example_sdss(): # Read SDSS DR12 galaxy spectrum taken from here http://dr12.sdss3.org/ # The spectrum is *already* log rebinned by the SDSS DR12 # pipeline and log_rebin should not be used in this case. file = 'spectra/NGC4636_SDSS_DR12.fits' hdu = pyfits.open(file) t = hdu['COADD'].data z = 0.003129 # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. mask = (t['loglam'] > np.log10(3540)) & (t['loglam'] < np.log10(7409)) flux = t['flux'][mask] galaxy = flux/np.median(flux) # Normalize spectrum to avoid numerical issues loglam_gal = t['loglam'][mask] lam_gal = 10**loglam_gal noise = galaxy*0 + 0.0166 # Assume constant noise per pixel here c = 299792.458 # speed of light in km/s frac = lam_gal[1]/lam_gal[0] # Constant lambda fraction per pixel dlam_gal = (frac - 1)*lam_gal # Size of every pixel in Angstrom wdisp = t['wdisp'][mask] # Intrinsic dispersion of every pixel, in pixels units fwhm_gal = 2.355*wdisp*dlam_gal # Resolution FWHM of every pixel, in Angstroms velscale = np.log(frac)*c # Constant velocity scale in km/s per pixel # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # lam_gal = lam_gal/(1+z) # Compute approximate restframe wavelength # fwhm_gal = fwhm_gal/(1+z) # Adjust resolution in Angstrom # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob('miles_models/Mun1.30Z*.fits') vazdekis.sort() fwhm_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SDSS galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = pyfits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lam_temp = h2['CRVAL1'] + h2['CDELT1']*np.arange(h2['NAXIS1']) lamRange_temp = [np.min(lam_temp), np.max(lam_temp)] sspNew, logLam2, velscale = util.log_rebin(lamRange_temp, ssp, velscale=velscale) templates = np.empty((sspNew.size, len(vazdekis))) # Interpolates the galaxy spectral resolution at the location of every pixel # of the templates. Outside the range of the galaxy spectrum the resolution # will be extrapolated, but this is irrelevant as those pixels cannot be # used in the fit anyway. fwhm_gal = np.interp(lam_temp, lam_gal, fwhm_gal) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SDSS and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SDSS # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # # In the line below, the fwhm_dif is set to zero when fwhm_gal < fwhm_tem. # In principle it should never happen and a higher resolution template should be used. # fwhm_dif = np.sqrt((fwhm_gal**2 - fwhm_tem**2).clip(0)) sigma = fwhm_dif/2.355/h2['CDELT1'] # Sigma difference in pixels for j, fname in enumerate(vazdekis): hdu = pyfits.open(fname) ssp = hdu[0].data ssp = util.gaussian_filter1d(ssp, sigma) # perform convolution with variable sigma sspNew, logLam2, velscale = util.log_rebin(lamRange_temp, ssp, velscale=velscale) templates[:, j] = sspNew/np.median(sspNew) # Normalizes 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 (see above). # c = 299792.458 dv = np.log(lam_temp[0]/lam_gal[0])*c # km/s goodpixels = util.determine_goodpixels(np.log(lam_gal), lamRange_temp, z) # 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. # vel = c*np.log(1 + z) # Initial estimate of the galaxy velocity in km/s start = [vel, 200.] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=12, vsyst=dv, clean=False) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
goodpixels = util.determine_goodpixels(log_wave, lamRange_temp, z) start = [c * np.log(1 + z), 0.01] #3*velscale] fixed = [True, True] #, False, False] fixed = [False, False] print start pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=False, moments=2, degree=-1, vsyst=dv, clean=False, regul=1, mdegree=0, reddening=0.1, lam=wave, fixed=fixed) # noise *= np.sqrt(pp.chi2) # pp = ppxf(templates, galaxy, noise, velscale, start, # goodpixels=goodpixels, plot=False, moments=2, degree=-1, # vsyst=dv, clean=False, regul=0., # mdegree=0, reddening=None,lam=wave # )
def ppxf_kinematics_example_sauron(): # Read a galaxy spectrum and define the wavelength range # file = 'spectra/NGC4550_SAURON.fits' hdu = fits.open(file) gal_lin = hdu[0].data h1 = hdu[0].header lamRange1 = h1['CRVAL1'] + np.array( [0., h1['CDELT1'] * (h1['NAXIS1'] - 1)]) FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum before the usual LOG_REBIN, given that a # red shift corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_lin) galaxy = galaxy / np.median( galaxy) # Normalize spectrum to avoid numerical issues noise = galaxy * 0 + 0.0047 # Assume constant noise per pixel here # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob('miles_models/Mun1.30Z*.fits') FWHM_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = fits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array( [0., h2['CDELT1'] * (h2['NAXIS1'] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size, len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif / 2.355 / h2['CDELT1'] # Sigma difference in pixels for j, file in enumerate(vazdekis): hdu = fits.open(file) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes 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 (see above). # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 450. # Initial estimate of the galaxy velocity in km/s z = np.exp(vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # 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. # start = [vel, 180., 0, 0] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
def ppxf_population_gas_example_sdss(quiet=True): galaxy='ic1459' z = 0.005 wav_range='4200-' out_dir = '%s/Data/vimos/analysis' % (cc.base_dir) output = "%s/%s/results/%s" % (out_dir, galaxy, wav_range) out_plots = "%splots" % (output) out_pickle = '%s/pickled' % (output) pickleFile = open("%s/dataObj_%s_pop.pkl" % (out_pickle, wav_range), 'rb') #pickleFile = open("%s/dataObj_%s.pkl" % (cc.home_dir, wav_range), 'rb') D = pickle.load(pickleFile) pickleFile.close() #------------------- Setup templates ----------------------- c = 299792.458 # speed of light in km/s velscale = np.log(D.bin[100].lam[1]/D.bin[100].lam[0])*c FWHM_gal = 4*0.71 # SDSS has an instrumental resolution FWHM of 2.76A. stars_templates, lamRange_temp, logLam_temp = \ setup_spectral_library(velscale, FWHM_gal) # The stellar templates are reshaped 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 = stars_templates.shape[1:] stars_templates = stars_templates.reshape(stars_templates.shape[0],-1) # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # stars_templates /= np.median(stars_templates) # Normalizes stellar templates by a scalar regul_err = 0.004 # Desired regularization error w=[] for bin_number in range(D.number_of_bins): bin_number=32 print(bin_number,'/',D.number_of_bins) # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # b=D.bin[bin_number] # file = 'spectra/NGC3522_SDSS.fits' # hdu = pyfits.open(file) # t = hdu[1].data # z = float(hdu[1].header["Z"]) # SDSS redshift estimate # # Only use the wavelength range in common between galaxy and stellar library. # # # mask = (t.field('wavelength') > 3540) & (t.field('wavelength') < 7409) # galaxy = t[mask].field('flux')/np.median(t[mask].field('flux')) # Normalize spectrum to avoid numerical issues # wave = t[mask].field('wavelength') mask = (b.lam > 3540) & (b.lam < 7409) galaxy = b.spectrum/np.median(b.spectrum) # Normalize spectrum to avoid numerical issues wave = b.lam # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0). # A constant error is not a bad approximation in the fitted wavelength # range and reduces the noise in the fit. # noise = galaxy*0 + 0.01528 # Assume constant noise per pixel here # Construct a set of Gaussian emission line templates. # Estimate the wavelength fitted range in the rest frame. # lamRange_gal = np.array([np.min(wave), np.max(wave)])/(1 + z) gas_templates, line_names, line_wave = \ util.emission_lines(logLam_temp, lamRange_gal, FWHM_gal, quiet=quiet) # 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.hstack([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_KINEMATICS_EXAMPLE_SAURON. # dv = (np.log(lamRange_temp[0])-np.log(wave[0]))*c # km/s vel = c*z # Initial estimate of the galaxy velocity in km/s # Here the actual fit starts. The best fit is plotted on the screen. # # 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. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] # Assign component=0 to the stellar templates and # component=1 to the gas emission lines templates. # One can easily assign different kinematic components to different gas species # e.g. component=1 for the Balmer series, component=2 for the [OIII] doublet, ...) # Input a negative MOMENTS value to keep fixed the LOSVD of a component. # nTemps = stars_templates.shape[1] nLines = gas_templates.shape[1] component = [0]*nTemps + [1]*nLines moments = [4, 2] # fit (V,sig,h3,h4) for the stars and (V,sig) for the gas start = [start, start] # adopt the same starting value for both gas and stars pp = ppxf(templates, galaxy, noise, velscale, start, plot=True, moments=moments, degree=-1, mdegree=10, vsyst=dv, clean=False, regul=1./regul_err, reg_dim=reg_dim, component=component, quiet=quiet) plt.subplot(212) weights = pp.weights[:np.prod(reg_dim)].reshape(reg_dim)/pp.weights.sum() plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', origin='upper', extent=[np.log10(1), np.log10(17.7828), -1.9, 0.45]) plt.colorbar() plt.title("Mass Fraction") plt.xlabel("log$_{10}$ Age (Gyr)") plt.ylabel("[M/H]") plt.tight_layout() plt.show() # Plot fit results for stars and gas weights = pp.weights[:np.prod(reg_dim)].reshape(reg_dim)/pp.weights.sum() w.append(weights) w = np.array(w) pickleFile = open("pop.pkl", 'wb') pickle.dump(w,pickleFile) pickleFile.close()
def run_ppxf(spectra, velscale, ncomp=None, has_emission=True, mdegree=-1, degree=20, plot=False, sky=None, start=None, moments=None, log_dir=None, w1=4000., w2=7000.): """ Run pPXF in a list of spectra""" if isinstance(spectra, str): spectra = [spectra] ########################################################################## # Load templates for both stars and gas star_templates = pf.getdata(os.path.join(templates_dir, 'miles_FWHM_3.7.fits'), 0) logLam2 = pf.getdata(os.path.join(templates_dir, 'miles_FWHM_3.7.fits'), 1) miles = np.loadtxt(os.path.join(templates_dir, 'miles_FWHM_3.7.txt'), dtype=str).tolist() gas_templates = pf.getdata(os.path.join(templates_dir, 'emission_FWHM_3.7.fits'), 0) logLam_gas = pf.getdata(os.path.join(templates_dir, 'emission_FWHM_3.7.fits'), 1) gas_files = np.loadtxt(os.path.join(templates_dir, 'emission_FWHM_3.7.txt'), dtype=str).tolist() ngas = len(gas_files) ntemplates = len(miles) ########################################################################## # Join templates in case emission lines are used. if has_emission: templates = np.column_stack((star_templates, gas_templates)) templates_names = np.hstack((miles, gas_files)) else: templates = star_templates templates_names = miles ngas = 0 ########################################################################## if sky == None: nsky = 0 else: nsky = sky.shape[1] if ncomp == 1: components = 0 elif ncomp == 2: components = np.hstack((np.zeros(len(star_templates[0])), np.ones(len(gas_templates[0])))) if moments == None: moments = [4] if ncomp == 1 else [4,2] for i, spec in enumerate(spectra): print "pPXF run of spectrum {0} ({1} of {2})".format(spec, i+1, len(spectra)) plt.clf() ###################################################################### # Read galaxy spectrum and define the wavelength range hdu = pf.open(spec) spec_lin = hdu[0].data h1 = pf.getheader(spec) lamRange1 = h1['CRVAL1'] + np.array([0.,h1['CDELT1']*(h1['NAXIS1']-1)]) ###################################################################### # Rebin to log scale galaxy, logLam1, velscale = util.log_rebin(lamRange1, spec_lin, velscale=velscale) ###################################################################### # First guess for the noise noise = np.ones_like(galaxy) * np.std(galaxy - medfilt(galaxy, 5)) ###################################################################### # Calculate difference of velocity between spectrum and templates # due to different initial wavelength dv = (logLam2[0]-logLam1[0])*c ###################################################################### # Set first guess from setup files if start is None: start = [v0s[spec.split("_")[0]], 50] goodPixels = None ###################################################################### # Expand start variable to include multiple components if ncomp > 1: start = [start, [start[0], 30]] ###################################################################### # Select goodpixels w = np.exp(logLam1) goodpixels = np.argwhere((w>w1) & (w<w2)).T[0] # First pPXF interaction pp0 = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=False, moments=moments, degree=degree, mdegree=mdegree, vsyst=dv, component=components, sky=sky) rms0 = galaxy[goodpixels] - pp0.bestfit[goodpixels] noise0 = 1.4826 * np.median(np.abs(rms0 - np.median(rms0))) noise0 = np.zeros_like(galaxy) + noise0 # Second pPXF interaction, realistic noise estimation pp = ppxf(templates, galaxy, noise0, velscale, start, goodpixels=goodpixels, plot=False, moments=moments, degree=degree, mdegree=mdegree, vsyst=dv, component=components, sky=sky) plt.title(spec.replace("_", "-")) plt.show(block=False) plt.savefig("{1}/{0}".format(spec.replace(".fits", ".png"), log_dir)) ###################################################################### # Adding other things to the pp object pp.template_files = templates_names pp.has_emission = has_emission pp.dv = dv pp.w = w pp.velscale = velscale pp.spec = spec pp.ngas = ngas pp.ntemplates = ntemplates pp.nsky = nsky pp.templates = 0 ###################################################################### # Save fit to pickles file to keep session ppsave(pp, "{1}/{0}".format(spec.replace(".fits", ""), log_dir)) pp = ppload("{1}/{0}".format(spec.replace(".fits", ""), log_dir)) ###################################################################### ppf = pPXF(spec, velscale, pp) ppf.plot("{1}/{0}".format(spec.replace(".fits", ".png"), log_dir)) ###################################################################### # # Save to output text file # if ncomp > 1: # pp.sol = pp.sol[0] # pp.error = pp.error[0] # sol = [val for pair in zip(pp.sol, pp.error) for val in pair] # sol = ["{0:12s}".format("{0:.3g}".format(x)) for x in sol] # sol.append("{0:12s}".format("{0:.3g}".format(pp0.chi2))) # sol = ["{0:30s}".format(spec)] + sol return
def ppxf_population_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS_DR8.fits' hdu = fits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t['wavelength'] > 3540) & (t['wavelength'] < 7409) flux = t['flux'][mask] galaxy = flux / np.median( flux) # Normalize spectrum to avoid numerical issues wave = t['wavelength'][mask] # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0) # noise = galaxy * 0 + 0.01528 # 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 = np.log(wave[1] / wave[0]) * c FWHM_gal = 2.76 # SDSS has an instrumental resolution FWHM of 2.76A. templates, lamRange_temp, logAge_grid, metal_grid = \ setup_spectral_library(velscale, FWHM_gal) # 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_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = c * np.log(lamRange_temp[0] / wave[0]) # km/s goodpixels = util.determine_goodpixels(np.log(wave), lamRange_temp, z) # Here the actual fit starts. The best fit is plotted on the screen. # # 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. # vel = c * np.log(1 + z) # Initial estimate of the galaxy velocity in km/s start = [vel, 180., 0, 0] # (km/s), starting guess for [V,sigma] # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # templates /= np.median(templates) # Normalizes templates by a scalar regul_err = 0.004 # Desired regularization error t = clock() plt.clf() plt.subplot(211) pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=-1, vsyst=dv, clean=False, mdegree=10, regul=1. / regul_err) # When the two numbers below are the same, the solution is the smoothest # consistent with the observed spectrum. # print('Desired Delta Chi^2: %.4g' % np.sqrt(2 * goodpixels.size)) print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1) * goodpixels.size)) print('Elapsed time in PPXF: %.2f s' % (clock() - t)) print('Mass-weighted <logAge> [Gyr]: %.3g' % (np.sum(pp.weights * logAge_grid.ravel()) / np.sum(pp.weights))) print('Mass-weighted <[M/H]>: %.3g' % (np.sum(pp.weights * metal_grid.ravel()) / np.sum(pp.weights))) plt.subplot(212) s = templates.shape weights = pp.weights.reshape(s[1:]) / pp.weights.sum() plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', origin='upper', extent=[np.log10(1), np.log10(17.7828), -1.9, 0.45]) plt.colorbar() plt.title("Mass Fraction") plt.xlabel("log$_{10}$ Age (Gyr)") plt.ylabel("[M/H]") plt.tight_layout() plt.show()
def ppxf_CaT(wavelengths, templates, fluxes, sigmas, vel_scale, delta_v, start_rv, start_sigma, goodpixels, quiet): ''' Run pPXF on a given spectrum This function runs pPXF on a spectrum using the given templates and returns the fitted spectrum, radial velocity, velocity dispersion and CaT index strength. Parameters ---------- wavelengths : numpy array Linearly dispersed input wavelengths templates : numpy array Logarithmically dispersed fluxes of templates. The shape of the array is (M, N) where M is number of pixels in a template and N is the number of templates fluxes : numpy array Logarithmically dispersed input fluxes sigmas : numpy array Logarithmically dispersed input sigmas vel_scale : float Natural logarithmic dispersion converted into velocity delta_v : float Difference in natural logarithmic starting wavelengths of the input spectrum and the templates in velocity start_rv : float Starting radial velocity for fit start_sigma : float Starting velocity dispersion for fit goodpixels : numpy array Indices of pixels to be fit quite : bool Whether pPXF should be verbose or not Returns ------- log_output_fluxes : numpy array Logarithmically dispersed fitted fluxes output_wavelengths : numpy array Linearly dispersed fitted wavelengths output_fluxes : numpy array Linear dispersed fitted fluxes normed_fluxes : numpy array Continuum normalised fluxes rv : float Fitted radial velocity sigma : float Fitted velocity dispersion CaT : float Measured CaT strength ''' output = ppxf.ppxf(templates, fluxes, sigmas, vel_scale, moments=2, mdegree=7, degree=-1, clean=True, vsyst=delta_v, start=[start_rv, start_sigma], quiet=quiet, goodpixels=goodpixels) #convert fitted spectra to linear dispersion and de-redshift log_output_fluxes = output.bestfit output_wavelengths, output_fluxes = interp.logtolinear(wavelengths, log_output_fluxes, function='nearest', ratio=True) rv = output.sol[0] sigma = output.sol[1] zp1 = 1 + rv * 1e3 / constants.c output_wavelengths = output_wavelengths / zp1 #normalise fitted spectrum and measure CaT strength normed_fluxes = normalisation.normalise(output_wavelengths, output_fluxes, 8, normalisation.newmask, True, .004, .010) CaT = indices.CaT_gc(output_wavelengths, normed_fluxes, normalized=True)[0] return log_output_fluxes, output_wavelengths, output_fluxes, normed_fluxes, rv, sigma, CaT
def ppxf_example_simulation(): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure hdu = fits.open(file_dir + '/veltemps/bd-013097_M2III_rebinflux_rest.fits' ) # BUCKET: which star should I choose??? # '/miles_models/Mun1.30Zp0.00T12.5893_iPp0.00_baseFe_linear_FWHM_2.51.fits') # Solar metallicitly, Age=12.59 Gyr ssp = hdu[ 1].data # hdu[1] is science header for us; hdu[0] is generic header h = hdu[1].header lamRange = h['CRVAL1'] + np.array([0., h['CD1_1'] * (h['NAXIS1'] - 1)]) c = 299792.458 # speed of light in km/s velscale = c * h['CD1_1'] / max( lamRange) # Do not degrade original velocity sampling star, logLam, velscale = util.log_rebin(lamRange, ssp, velscale=velscale) # The finite sampling of the observed spectrum is modeled in detail: # the galaxy spectrum is obtained by oversampling the actual observed spectrum # to a high resolution. This represent the true spectrum, which is later resampled # to lower resolution to simulate the observations on the CCD. Similarly, the # convolution with a well-sampled LOSVD is done on the high-resolution spectrum, # and later resampled to the observed resolution before fitting with PPXF. factor = 10 # Oversampling integer factor for an accurate convolution starNew = ndimage.interpolation.zoom( star, factor, order=3) # This is the underlying spectrum, known at high resolution star = rebin( starNew, factor ) # Make sure that the observed spectrum is the integral over the pixels # find worst SNR, and worst h3, h4 (highest) h3 = 0.1 # Adopted G-H parameters of the LOSVD h4 = 0.04 # 0.1 sn = 30. # Adopted S/N of the Monte Carlo simulation m = 300 # Number of realizations of the simulation moments = 4 velV = np.random.rand( m ) # velocity in *pixels* [=V(km/s)/velScale] # (len m array of random on [0, 1) sigmaV = np.linspace( 0.5, 4, m ) # Range of sigma in *pixels* [=sigma(km/s)/velScale] # m evenly spaced [0.5,...,4] result = np.zeros((m, moments)) # This will store the results t = clock() for j, (vel, sigma) in enumerate(zip(velV, sigmaV)): dx = int( abs(vel) + 5 * sigma) # Sample the Gaussian and GH at least to vel+5*sigma x = np.linspace( -dx, dx, 2 * dx * factor + 1) # Evaluate the Gaussian using steps of 1/factor pixels. w = (x - vel) / sigma w2 = w**2 gauss = np.exp(-0.5 * w2) gauss /= np.sum(gauss) # Normalized total(gauss)=1 h3poly = w * (2. * w2 - 3.) / np.sqrt(3.) # H3(y) h4poly = (w2 * (4. * w2 - 12.) + 3.) / np.sqrt(24.) # H4(y) losvd = gauss * (1. + h3 * h3poly + h4 * h4poly) galaxy = signal.fftconvolve( starNew, losvd, mode="same") # Convolve the oversampled spectrum # FFT convolution: multiplication in the frequency domain corresponds to convolution in the time domain galaxy = rebin( galaxy, factor) # Integrate spectrum into original spectral pixels noise = galaxy / sn # 1sigma error spectrum galaxy = np.random.normal(galaxy, noise) # Add noise to the galaxy spectrum start = np.array([ vel + np.random.uniform(-1, 1), sigma * np.random.uniform(0.8, 1.2) ]) * velscale # Convert to km/s pp = ppxf(star, galaxy, noise, velscale, start, goodpixels=np.arange(dx, galaxy.size - dx), plot=False, moments=moments, bias=0.4) # 0.2 result[j, :] = pp.sol print('Calculation time: %.2f s' % (clock() - t)) # large scale kinematics to estimate dark matter fraction within effective radii, and stellar mass-light ratio # mass distribution discribed by GME and dark matter dstribution different # work with sarah to use JAM; figure out why we ever use schwarzschild model if JAM is faster (what assumptions are # we making/what regimes do we have to be in to use JAM? plt.clf() plt.subplot(221) plt.plot(sigmaV * velscale, result[:, 0] - velV * velscale, '+k') # BUCKET: why is V multiplied by velscale? plt.axhline(0, color='r') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-20, 20) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel(r'$V - V_{\rm in}\ (km\ s^{-1})$') plt.text(2.05 * velscale, -15, r'2$\times$velscale') plt.subplot(222) plt.plot(sigmaV * velscale, result[:, 1] - sigmaV * velscale, '+k') # BUCKET: why is sigma multiplied by velscale? plt.axhline(0, color='r') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-20, 20) plt.xlabel(r'$\sigma_{in}\ (km\ s^{-1})$') plt.ylabel(r'$\sigma - \sigma_{\rm in}\ (km\ s^{-1})$') plt.text(2.05 * velscale, -15, r'2$\times$velscale') plt.subplot(223) plt.plot(sigmaV * velscale, result[:, 2], '+k') plt.axhline(h3, color='r') plt.axhline(0, linestyle='dotted', color='limegreen') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-0.2 + h3, 0.2 + h3) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel('$h_3$') plt.text(2.05 * velscale, h3 - 0.15, r'2$\times$velscale') plt.subplot(224) plt.plot(sigmaV * velscale, result[:, 3], '+k') plt.axhline(h4, color='r') plt.axhline(0, linestyle='dotted', color='limegreen') plt.axvline(velscale, linestyle='dashed') plt.axvline(2 * velscale, linestyle='dashed') plt.ylim(-0.2 + h4, 0.2 + h4) plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$') plt.ylabel('$h_4$') plt.text(2.05 * velscale, h4 - 0.15, r'2$\times$velscale') plt.tight_layout() plt.show() # pause(1)
def ppxf_nifs_kinematics(newgal=None, resid=None, centers=None, coords=[0, 0]): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure # Read a galaxy spectrum and define the wavelength range file = file_dir + '/spectra/pgc12557_combined.fits' # '/spectra/NGC4550_SAURON.fits' # my current file location hdu = fits.open(file) gal_lin = hdu[1].data # gal_lin = hdu[0].data h1 = hdu[ 1].header # I need to use 1st extension header (0=general, 1=science, 2=variance, 3=data quality flags) lamRange1 = h1['CRVAL3'] + np.array( [0., h1['CD3_3'] * (h1['NAXIS3'] - 1)]) # [ 19971.86914062 24319.31070422] print(lamRange1, 'l1') # print(gal_lin[0][20]) all 0s # print((gal_lin[300][35])) NOT all 0s! # len(gal_lin) = 2040, len(gal_lin[0]) = 69, len(gal_lin[0][1]) = 71 # 2040 --> NAXIS 3, 69 --> NAXIS2, 71 --> NAXIS1 # There's a len 2040 spectrum at each gal_lin[:,x,y] --> gal_lin[:, x, y] is an array len(2040) starting at # lamRange1[0] and finishing at lamRange1[1], with each pixel in between separated by h1['CD3_3']. # CUT SPECTRUM TO WAVELENGTH RANGE 2.26 - 2.42 low_lim = 22600. up_lim = 24200. cut1 = int( (low_lim - lamRange1[0]) / h1['CD3_3'] ) # num pixels between pix 1 & pix corresponding to 2.26 microns cut2 = int( (up_lim - lamRange1[0]) / h1['CD3_3'] ) # num pixels between pix 1 & pix corresponding to 2.42 microns # print(cut1, cut2, 'cuts') # 1232.62354281, 1983.04191009 --> int(cut1) = 1232, int(cut2) = 1983 gal_lin = gal_lin[cut1: cut2] # cut gal_lin spectrum to new wavelength range start = h1['CRVAL3'] + h1['CD3_3'] * cut1 stop = h1['CRVAL3'] + h1['CD3_3'] * cut2 lamRange1 = [start, stop ] # redefine lamRange1 to correspond to new wavelength range print(lamRange1, 'l1, cut') # len(gal_lin) is now NOT 2040 but 1983 - 1233 = 750 FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # BUCKET: do I need this? If so what for? # If the galaxy is at significant redshift, one should bring the galaxy # spectrum roughly to the rest-frame wavelength, before calling pPXF # (See Sec2.4 of Cappellari 2017). In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # There's a len 2040 spectrum at each gal_lin[:,x,y] x = coords[0] y = coords[1] # if newgal is None: galaxy, logLam1, velscale = util.log_rebin( lamRange1, gal_lin[:, x, y]) # no input velscale --> fcn returns it print(len(galaxy), 'len gal') # 750 now because of cut to gal_lin! print(np.median(galaxy)) galaxy = galaxy / np.median( galaxy) # Normalize spectrum to avoid numerical issues # else: # galaxy, logLam1, velscale = util.log_rebin(lamRange1, newgal) # newgal is the spectrum at coords x, y # basically bootstrap the noise! # Do one fit with flat noise, then save the best fit spectrum and the residuals. # Then, iterate ~200 times. For these iterations, set bias = 0.0. Each iteration, for each pixel, use the spectrum # value as the center of a gaussian and use the residuals at that pixel value as the width of the gaussian. Draw # from the resultant distribution to make the new noise. For each iteration, save the output V, sigma, h3, h4, and # print each spectrum so that we can see it evolving (it should look more like a real spectrum, rather than smooth # curve without lines) noise = np.full_like(galaxy, 0.0047) # Assume constant noise per pixel here # MEANTIME: why is output velocity close to systemic insted of close to 0, if I'm dealing with redshift already? # SET bias = 0 # print('shape', noise.shape) # 751, # print(galaxy.shape) # 751, # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob( file_dir + '/veltemps/*.fits') # '/miles_models/Mun1.30Z*.fits') # my new # BUCKET: what are FWHM of spectra in the veltemps library?? FWHM_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # BUCKET: what spectral sampling compared to galaxy do we want for templates?? # velscale_ratio = 2 # 1.23 # 2 # adopts 2x higher spectral sampling for templates than for galaxy # PROBLEM!! If velscale_ratio is not integer, we get issue later because we slice a list with velscale_ratio # so need to change velscale? But it's set by util.log_rebin originally! Only need this if oversampling the # templates, which I'm not doing # Extract the wavelength range and logarithmically rebin one spectrum # to a velocity scale 2x smaller than the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # print(vazdekis[0], 'template name') hdu = fits.open( vazdekis[0] ) # do for just one template to determine size needed for array containing all templates ssp = hdu[ 1].data # was hdu[0], but that's generic header rather than science header h2 = hdu[1].header # was hdu[0] lamRange2 = h2['CRVAL1'] + np.array([0., h2['CD1_1'] * (h2['NAXIS1'] - 1) ]) # BUCKET want NAXIS - 1? print(lamRange2, 'l2') # [ 20628.29 24291.89] # print((lamRange2[1] - lamRange2[0])/h2['CD1_1'], 'num of steps in lam2') # 1720. # print(len(ssp), 'len ssp') # 1721 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(len(sspNew), 'len sspnew') # 622 hmmmm NEED THIS TO BE >= (len(galaxy)=750) # FIXED, now 1791 templates = np.empty((sspNew.size, len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) # sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels sigma = FWHM_dif / 2.355 / h2['CD1_1'] # Sigma difference in pixels for j, file in enumerate( vazdekis ): # now for each template file; so why do the thing above with just 1?? hdu = fits.open(file) # ssp = hdu[0].data ssp = hdu[1].data ssp = ndimage.gaussian_filter1d(ssp, sigma) # ndimage.gaussian_filter takes input array (ssp) and filters it. Sigma = standard deviation of Gaussian kernel # used to filter the array # note: discrete convolution is defined (for any 2 arrays a, v): (a*v)[n] == sum m(-inf to inf) of a[m]*v[n-m] # where a is the original curve and v is the gaussian # print(len(ssp)) # 1721 --> currently by default being resampled to be 116, want it to be resampled to be 71 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(velscale_temp, 'vt') # 78.82967746 # print(velscale, 'v') # 78.82967746 # print(len(sspNew)) # need this to be >= len(galaxy) # now 1791 templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates # print(len(templates[0])) # len(templates)=29, len(templates[0] = 19) # 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 (see above). # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s ''' if velscale_ratio > 1: dv = (np.mean(logLam2[:velscale_ratio]) - logLam1[0])*c # km/s else: dv = (logLam2[0] - logLam1[0])*c # km/s ''' z = 0.016561 # z = 0.0015 # Initial redshift estimate of the galaxy goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # 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. # vel = c * np.log(1 + z) # eq.(8) of Cappellari (2017) start = [vel, 200.] # [vel, 200.] # (km/s), starting guess for [V, sigma] t = clock() # print(galaxy.shape[0]) # = len(galaxy) = 750 # print(goodPixels) # print(max(noise[:, x, y]), min(noise[:, x, y]), np.median(noise[:, x, y]), np.mean(noise[:, x, y])) # 0.00106575 -2.77079e-05 4.62628e-05 5.89877e-05 if newgal is None: pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv, velscale_ratio=1, bias=0.) # velscale_ratio=velscale_ratio) stuff = pp.bestfit, pp.galaxy, pp.sol, goodPixels else: pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv, velscale_ratio=1, bias=0.) # velscale_ratio=velscale_ratio) pp_new = ppxf(templates, newgal, noise, velscale, start, goodpixels=goodPixels, plot=False, moments=4, degree=4, vsyst=dv, velscale_ratio=1, bias=0.) # velscale_ratio=velscale_ratio) # pp.plot() # Plot best fit and gas lines x_ax = np.arange(galaxy.size) plt.plot(x_ax, pp.galaxy, 'k') plt.plot(x_ax, newgal, 'b') stuff = pp_new.bestfit, pp_new.galaxy, pp_new.sol print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) print('Elapsed time in pPXF: %.2f s' % (clock() - t)) # If the galaxy is at significant redshift z and the wavelength has been # de-redshifted with the three lines "z = 1.23..." near the beginning of # this procedure, the best-fitting redshift is now given by the following # commented line (equation 2 of Cappellari et al. 2009, ApJ, 704, L34; # http://adsabs.harvard.edu/abs/2009ApJ...704L..34C) # # print('Best-fitting redshift z:', (z + 1)*(1 + pp.sol[0]/c) - 1) return stuff # output_spectrum, output_noise # for use in noise_in
def run_ppxf(spectra, velscale, ncomp=2, has_emission=True, mdegree=-1, degree=20, pkls=None, plot=False, data_sky=None): """ Run pPXF in a list of spectra""" if isinstance(spectra, str): spectra = [spectra] if isinstance(pkls, str): pkls = [pkls] if pkls == None: pkls = [x.replace(".fits", ".pkl") for x in spectra] ########################################################################## # Load templates for both stars and gas star_templates, logLam2, delta, miles = stellar_templates(velscale) gas_templates, logLam_gas, delta_gas, gas_files = emission_templates( velscale) ########################################################################## # Join templates in case emission lines are used. if has_emission: templates = np.column_stack((star_templates, gas_templates)) else: templates = star_templates ########################################################################## if ncomp == 1: components = 0 moments = [4] templates_names = miles elif ncomp == 2: components = np.hstack( (np.zeros(len(star_templates[0])), np.ones(len(gas_templates[0])))) moments = [4, 2] templates_names = np.hstack((miles, gas_files)) else: raise Exception("ncomp has to be 1 or 2.") for i, spec in enumerate(spectra): print "pPXF run of spectrum {0} ({1} of {2})".format( spec, i + 1, len(spectra)) pkl = pkls[i] ###################################################################### # Read one galaxy spectrum and define the wavelength range specfile = os.path.join(data_dir, spec) hdu = pf.open(specfile) spec_lin = hdu[0].data h1 = pf.getheader(specfile) lamRange1 = h1['CRVAL1'] + np.array( [0., h1['CDELT1'] * (h1['NAXIS1'] - 1)]) ###################################################################### # Degrade observed spectra to match template resolution FWHM_dif = np.sqrt(FWHM_tem**2 - FWHM_spec**2) sigma = FWHM_dif / 2.355 / delta # Sigma difference in pixels spec_lin = ndimage.gaussian_filter1d(spec_lin, sigma) ###################################################################### # Rebin to log scale galaxy, logLam1, velscale = util.log_rebin(lamRange1, spec_lin, velscale=velscale) ###################################################################### # First guess for the noise noise = np.ones_like(galaxy) * np.std(galaxy - medfilt(galaxy, 5)) ###################################################################### # Calculate difference of velocity between spectrum and templates # due to different initial wavelength dv = (logLam2[0] - logLam1[0]) * c ###################################################################### # Set first guess from setup files start, goodPixels = read_setup_file(spec, logLam1, mask_emline=False) ###################################################################### # Expand start variable to include multiple components if ncomp > 1: start = [start, [start[0], 30]] ###################################################################### # Read sky in needed if data_sky == None: sky = None else: sky_lin = pf.getdata(data_sky[i]) sky_lin = ndimage.gaussian_filter1d(sky_lin, sigma) sky, logLam1, velscale = util.log_rebin(lamRange1, sky_lin, velscale=velscale) sky = sky.reshape(-1, 1) ###################################################################### # First pPXF interaction if os.path.exists(spec.replace(".fits", ".pkl")): pp0 = pPXF(spec, velscale, pklfile=spec.replace(".fits", ".pkl")) noise0 = pp0.noise else: pp0 = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=False, moments=moments, degree=12, mdegree=-1, vsyst=dv, component=components, sky=sky) rms0 = galaxy[goodPixels] - pp0.bestfit[goodPixels] noise0 = 1.4826 * np.median(np.abs(rms0 - np.median(rms0))) noise0 = np.zeros_like(galaxy) + noise0 # Second pPXF interaction, realistic noise estimation pp = ppxf(templates, galaxy, noise0, velscale, start, goodpixels=goodPixels, plot=plot, moments=moments, degree=degree, mdegree=mdegree, vsyst=dv, component=components, sky=sky) # pp.template_files = templates_names # pp.has_emission = has_emission ###################################################################### # Save to output file to keep session with open(pkl, "w") as f: pickle.dump(pp, f) ###################################################################### return
def ppxf_population_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS.fits' hdu = pyfits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t.field('wavelength') > 3540) & (t.field('wavelength') < 7409) galaxy = t[mask].field('flux')/np.median(t[mask].field('flux')) # Normalize spectrum to avoid numerical issues wave = t[mask].field('wavelength') # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0) # noise = galaxy*0 + 0.01528 # 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 = np.log(wave[1]/wave[0])*c FWHM_gal = 2.76 # SDSS has an instrumental resolution FWHM of 2.76A. templates, lamRange_temp = setup_spectral_library(velscale, FWHM_gal) # 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_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = (np.log(lamRange_temp[0])-np.log(wave[0]))*c # km/s vel = c*z # Initial estimate of the galaxy velocity in km/s goodpixels = util.determine_goodpixels(np.log(wave),lamRange_temp,vel) # Here the actual fit starts. The best fit is plotted on the screen. # # 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. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # templates /= np.median(templates) # Normalizes templates by a scalar regul_err = 0.004 # Desired regularization error t = clock() plt.clf() plt.subplot(211) pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=-1, vsyst=dv, clean=False, mdegree=10, regul=1./regul_err) # When the two numbers below are the same, the solution is the smoothest # consistent with the observed spectrum. # print('Desired Delta Chi^2: %.4g' % np.sqrt(2*goodpixels.size)) print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1)*goodpixels.size)) print('Elapsed time in PPXF: %.2f s' % (clock() - t)) plt.subplot(212) s = templates.shape print(s) weights = pp.weights.reshape(s[1],s[2])/pp.weights.sum() plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', extent=(np.log10(1.0), np.log10(17.7828), -1.9, 0.45)) plt.colorbar() plt.title("Mass Fraction") plt.xlabel("log$_{10}$ Age (Gyr)") plt.ylabel("[M/H]") plt.tight_layout() plt.show() vazdekis = glob.glob('miles_models/Mun1.30*.fits') '''
def find_best_template(wl_obs, flux, err, hdr, spectral_library): t2 = clock() # Read a spectrum and define the wavelength range obs_spectrum = flux obs_spectrum_header = hdr obs_error_spectrum = abs(err) obs_lambda_range = np.array([min(wl), max(wl)]) z = 0.0 # Initial estimate of the galaxy redshift obs_lambda_range = obs_lambda_range / ( 1 + z) # Compute approximate restframe wavelength range #Get median of positive values m = np.median(obs_error_spectrum[obs_error_spectrum > 0]) # Assign the median to the negative elements obs_error_spectrum[obs_error_spectrum <= 0] = m #logarithmically rebin while conserving flux tell_obs, obs_lambda, velscale = util.log_rebin(obs_lambda_range, obs_spectrum) tell_obs_err, obs_lambda, velscale = util.log_rebin( obs_lambda_range, obs_error_spectrum) # Normalize to avoid numerical issues norm = tell_obs[int(len(tell_obs) / 2)] tell_obs = tell_obs / norm tell_obs_err = tell_obs_err / norm # Load and prepare Model stellar library # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the target spectrum, to determine # the size needed for the array which will contain the template spectra. hdu = fits.open(spectral_library[0]) library_spectrum = hdu[0].data library_spectrum_header = hdu[0].header try: wl_lib = np.e**(np.arange((library_spectrum_header['NAXIS1'])) * library_spectrum_header['CDELT1'] + library_spectrum_header['CRVAL1']) except: wl_lib = fits.open( "/Users/jselsing/Work/spectral_libraries/phoenix_spectral_library/WAVE_PHOENIX-ACES-AGSS-COND-2011.fits" )[0].data #Make empty template holder f = splrep(wl_lib, library_spectrum, k=1) hdu_wave_short = splev(wl_obs, f) lib_lambda_range = np.array([min(wl_obs), max(wl_obs)]) tell_lib, lib_lambda, velscale = util.log_rebin(lib_lambda_range, hdu_wave_short, velscale=velscale) templates = np.empty((tell_lib.size, len(spectral_library))) # Convolve the whole library of spectral templates # with the quadratic difference between the target and the # library instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. for j in range(len(spectral_library)): t = clock() hdu = fits.open(spectral_library[j]) library_spectrum = hdu[0].data # Interpolate template spectrum to match input spectrum f = splrep(wl_lib, library_spectrum, k=1) interpolated_library_spectrum = splev(wl_obs, f) # Logarithmically rebin template spectra tell_lib, lib_lambda, velscale = util.log_rebin( lib_lambda_range, interpolated_library_spectrum, velscale=velscale) norm = tell_lib[int(len(tell_obs) / 2)] tell_lib = tell_lib / norm templates[:, j] = tell_lib / np.median(tell_lib) # Normalizes templates if j % 60 == 0: print 'Approximated remaining time (s) for setup of template spectra: ' + str( len(spectral_library) * (clock() - t) - j * (clock() - t)) + 's' #Excluding areas of strong telluric absorbtion from fitting if obs_spectrum_header['HIERARCH ESO SEQ ARM'] == "UVB": mask = (obs_lambda < np.log(5500)) & (obs_lambda > np.log(3100)) goodPixels = np.where(mask == True)[0] elif obs_spectrum_header['HIERARCH ESO SEQ ARM'] == "VIS": # mask = (obs_lambda > np.log(5500)) & (obs_lambda < np.log(6350)) | (obs_lambda > np.log(6380)) & (obs_lambda < np.log(6860)) | (obs_lambda > np.log(7045)) & (obs_lambda < np.log(7140)) | (obs_lambda > np.log(7355)) & (obs_lambda < np.log(7570)) | (obs_lambda > np.log(7710)) & (obs_lambda < np.log(8090)) | (obs_lambda > np.log(8400)) & (obs_lambda < np.log(8900)) | (obs_lambda > np.log(9900)) & (obs_lambda < np.log(10100)) mask = (obs_lambda > np.log(5550)) & (obs_lambda < np.log(5950)) | ( obs_lambda > np.log(6400) ) & (obs_lambda < np.log(6800)) | (obs_lambda > np.log(7355)) & ( obs_lambda < np.log(7570)) | (obs_lambda > np.log(8400)) & ( obs_lambda < np.log(8900)) | (obs_lambda > np.log(9900)) & ( obs_lambda < np.log(10100)) goodPixels = np.where(mask == True)[0] elif obs_spectrum_header['HIERARCH ESO SEQ ARM'] == "NIR": mask = (obs_lambda > np.log(10000)) & (obs_lambda < np.log(10950)) | ( obs_lambda > np.log(12240) ) & (obs_lambda < np.log(12500)) | (obs_lambda > np.log(12800)) & ( obs_lambda < np.log(12950)) | (obs_lambda > np.log(15300)) & ( obs_lambda < np.log(17100)) | (obs_lambda > np.log(21500)) & ( obs_lambda < np.log(22000)) & (tell_obs > 0) goodPixels = np.where(mask == True)[0] # Initial parameters for LOSVD c = 299792.458 dv = 15 #(logLam2[0]-logLam1[0])*c # km/s vel = -100 # Initial estimate of the LOSVD km/s start = [vel, dv] # (km/s), starting guess for [V,sigma] # Here the actual fit starts. print("Fitting ...") pp = ppxf.ppxf(templates, tell_obs, tell_obs_err, velscale, start, goodpixels=goodPixels, plot=False, moments=4, degree=3, mdegree=1) # pl.show() print "Formal errors:" print " dV dsigma dh3 dh4" print "".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2)) print 'elapsed time (s) for ppxf: ', clock() - t2 print 'Best-fitting R:', c / pp.sol[1] print 'Best-fitting error on R:', ( c / pp.sol[1] - c / (pp.sol[1] + pp.error[1] * np.sqrt(pp.chi2))) print(np.array(spectral_library)[np.ceil(pp.weights).astype("bool")]) # print(spectral_library[(np.array(pp.weights).astype("int")).astype("bool")]) print 'Rebinning to linear axes' f = interp1d(np.e**obs_lambda, pp.galaxy, bounds_error=False, fill_value=np.nan) obj_spec = f(wl_obs) f = interp1d(np.e**obs_lambda, pp.bestfit, bounds_error=False, fill_value=np.nan) template_fit = f(wl_obs) return obj_spec, template_fit, obs_spectrum_header