Exemplo n.º 1
0
def emission(dirfile, w1, f1, redshift, plateifu, tie_balmer, limit_doublets):
    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    z = redshift
    flux = f1
    galaxy = flux
    wave = w1

    wave *= np.median(util.vac_to_air(wave) / wave)

    noise = np.full_like(galaxy,
                         0.01635)  # Assume constant noise per pixel here

    c = 299792.458  # speed of light in km/s
    velscale = c * np.log(wave[1] / wave[0])  # eq.(8) of Cappellari (2017)
    # SDSS has an approximate instrumental resolution FWHM of 2.76A.
    FWHM_gal = 2.76

    # ------------------- Setup templates -----------------------

    pathname = ppxf_dir + '/miles_models/Mun1.30*.fits'

    # The templates are normalized to mean=1 within the FWHM of the V-band.
    # In this way the weights and mean values are light-weighted quantities
    miles = lib.miles(pathname, velscale, FWHM_gal)

    reg_dim = miles.templates.shape[1:]

    regul_err = 0.013  # Desired regularization error

    lam_range_gal = np.array([np.min(wave), np.max(wave)]) / (1 + z)
    gas_templates, gas_names, line_wave = util.emission_lines(
        miles.log_lam_temp,
        lam_range_gal,
        FWHM_gal,
        tie_balmer=tie_balmer,
        limit_doublets=limit_doublets)

    templates = gas_templates

    c = 299792.458
    dv = c * (miles.log_lam_temp[0] - np.log(wave[0])
              )  # eq.(8) of Cappellari (2017)
    vel = c * np.log(1 + z)  # eq.(8) of Cappellari (2017)
    start = [vel, 180.]  # (km/s), starting guess for [V, sigma]

    #     n_temps = stars_templates.shape[1]
    n_forbidden = np.sum(["[" in a
                          for a in gas_names])  # forbidden lines contain "[*]"
    n_balmer = len(gas_names) - n_forbidden

    component = [0] * n_balmer + [1] * n_forbidden
    gas_component = np.array(
        component) >= 0  # gas_component=True for gas templates

    moments = [2, 2]

    start = [start, start]

    gas_reddening = 0 if tie_balmer else None

    t = clock()
    pp = gas.ppxf(dirfile,
                  templates,
                  galaxy,
                  noise,
                  velscale,
                  start,
                  z,
                  plot=True,
                  moments=moments,
                  degree=-1,
                  mdegree=10,
                  vsyst=dv,
                  lam=wave,
                  clean=False,
                  component=component,
                  gas_component=gas_component,
                  gas_names=gas_names,
                  gas_reddening=gas_reddening)

    pp.plot()
    return pp.bestfit, pp.lam
Exemplo n.º 2
0
def ppxf_example_population_gas_sdss(tie_balmer, limit_doublets):

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    cube_id = 468

    # reading cube_data
    cube_file = "data/cubes/cube_" + str(cube_id) + ".fits"
    hdu = fits.open(cube_file)
    t = hdu[0].data

    # using our redshift estimate from lmfit
    cube_result_file = ("results/cube_" + str(cube_id) + "/cube_" +
                        str(cube_id) + "_lmfit.txt")
    cube_result_file = open(cube_result_file)

    line_count = 0
    for crf_line in cube_result_file:
        if (line_count == 20):
            curr_line = crf_line.split()
            z = float(curr_line[1])
        line_count += 1

    cube_x_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" +
                          str(int(cube_id)) + "_cbd_x.npy")
    cube_y_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" +
                          str(int(cube_id)) + "_cbs_y.npy")

    # Only use the wavelength range in common between galaxy and stellar library.
    #
    mask = (cube_x_data > 3540) & (cube_x_data < 7409)
    flux = cube_y_data[mask]
    galaxy = flux / np.median(
        flux)  # Normalize spectrum to avoid numerical issues
    wave = cube_x_data[mask]

    # The SDSS wavelengths are in vacuum, while the MILES ones are in air.
    # For a rigorous treatment, the SDSS vacuum wavelengths should be
    # converted into air wavelengths and the spectra should be resampled.
    # To avoid resampling, given that the wavelength dependence of the
    # correction is very weak, I approximate it with a constant factor.
    #
    wave *= np.median(util.vac_to_air(wave) / wave)

    # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0).
    # A constant noise is not a bad approximation in the fitted wavelength
    # range and reduces the noise in the fit.
    #
    noise = np.full_like(galaxy,
                         0.01635)  # Assume constant noise per pixel here

    # The velocity step was already chosen by the SDSS pipeline
    # and we convert it below to km/s
    #
    c = 299792.458  # speed of light in km/s
    velscale = c * np.log(wave[1] / wave[0])  # eq.(8) of Cappellari (2017)
    FWHM_gal = 2.76  # SDSS has an approximate instrumental resolution FWHM of 2.76A.

    #------------------- Setup templates -----------------------

    pathname = ppxf_dir + '/miles_models/Mun1.30*.fits'
    miles = lib.miles(pathname, velscale, FWHM_gal)

    # The stellar templates are reshaped below into a 2-dim array with each
    # spectrum as a column, however we save the original array dimensions,
    # which are needed to specify the regularization dimensions
    #
    reg_dim = miles.templates.shape[1:]
    stars_templates = miles.templates.reshape(miles.templates.shape[0], -1)

    # See the pPXF documentation for the keyword REGUL,
    regul_err = 0.013  # Desired regularization error

    # Construct a set of Gaussian emission line templates.
    # Estimate the wavelength fitted range in the rest frame.
    #
    lam_range_gal = np.array([np.min(wave), np.max(wave)]) / (1 + z)
    gas_templates, gas_names, line_wave = util.emission_lines(
        miles.log_lam_temp,
        lam_range_gal,
        FWHM_gal,
        tie_balmer=tie_balmer,
        limit_doublets=limit_doublets)

    # Combines the stellar and gaseous templates into a single array.
    # During the PPXF fit they will be assigned a different kinematic
    # COMPONENT value
    #
    templates = np.column_stack([stars_templates, gas_templates])

    #-----------------------------------------------------------

    # The galaxy and the template spectra do not have the same starting wavelength.
    # For this reason an extra velocity shift DV has to be applied to the template
    # to fit the galaxy spectrum. We remove this artificial shift by using the
    # keyword VSYST in the call to PPXF below, so that all velocities are
    # measured with respect to DV. This assume the redshift is negligible.
    # In the case of a high-redshift galaxy one should de-redshift its
    # wavelength to the rest frame before using the line below as described
    # in PPXF_EXAMPLE_KINEMATICS_SAURON and Sec.2.4 of Cappellari (2017)
    #
    c = 299792.458
    dv = c * (miles.log_lam_temp[0] - np.log(wave[0])
              )  # eq.(8) of Cappellari (2017)
    vel = c * np.log(1 + z)  # eq.(8) of Cappellari (2017)
    start = [vel, 180.]  # (km/s), starting guess for [V, sigma]

    n_temps = stars_templates.shape[1]
    n_forbidden = np.sum(["[" in a
                          for a in gas_names])  # forbidden lines contain "[*]"
    n_balmer = len(gas_names) - n_forbidden

    # Assign component=0 to the stellar templates, component=1 to the Balmer
    # gas emission lines templates and component=2 to the forbidden lines.
    component = [0] * n_temps + [1] * n_balmer + [2] * n_forbidden
    gas_component = np.array(
        component) > 0  # gas_component=True for gas templates

    # Fit (V, sig, h3, h4) moments=4 for the stars
    # and (V, sig) moments=2 for the two gas kinematic components
    moments = [4, 2, 2]

    # Adopt the same starting value for the stars and the two gas components
    start = [start, start, start]

    # If the Balmer lines are tied one should allow for gas reddeining.
    # The gas_reddening can be different from the stellar one, if both are fitted.
    gas_reddening = 0 if tie_balmer else None

    # Here the actual fit starts.
    #
    # IMPORTANT: Ideally one would like not to use any polynomial in the fit
    # as the continuum shape contains important information on the population.
    # Unfortunately this is often not feasible, due to small calibration
    # uncertainties in the spectral shape. To avoid affecting the line strength of
    # the spectral features, we exclude additive polynomials (DEGREE=-1) and only use
    # multiplicative ones (MDEGREE=10). This is only recommended for population, not
    # for kinematic extraction, where additive polynomials are always recommended.
    #
    t = clock()
    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              plot=False,
              moments=moments,
              degree=-1,
              mdegree=10,
              vsyst=dv,
              lam=wave,
              clean=False,
              regul=1. / regul_err,
              reg_dim=reg_dim,
              component=component,
              gas_component=gas_component,
              gas_names=gas_names,
              gas_reddening=gas_reddening)

    # When the two Delta Chi^2 below are the same, the solution
    # is the smoothest consistent with the observed spectrum.
    #
    print('Desired Delta Chi^2: %.4g' % np.sqrt(2 * galaxy.size))
    print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1) * galaxy.size))
    print('Elapsed time in PPXF: %.2f s' % (clock() - t))

    weights = pp.weights[
        ~gas_component]  # Exclude weights of the gas templates
    weights = weights.reshape(reg_dim) / weights.sum()  # Normalized

    miles.mean_age_metal(weights)
    miles.mass_to_light(weights, band="r")

    # Plot fit results for stars and gas.
    plt.clf()
    plt.subplot(211)
    pp.plot()

    # Plot stellar population mass fraction distribution
    plt.subplot(212)
    miles.plot(weights)
    plt.tight_layout()
    #plt.pause(1)
    plt.show()
Exemplo n.º 3
0
Arquivo: ppxf.py Projeto: FRBs/FRB
def fit_spectrum(spec,
                 zgal,
                 specresolution,
                 tie_balmer=False,
                 miles_dir=None,
                 rebin=True,
                 limit_doublets=False,
                 degree_add=None,
                 degree_mult=5,
                 **kwargs):
    """This is a wrapper for pPXF to fit stellar population models as well as
    emission lines to galaxy spectra.  Although pPXF allows otherwise, the
    emission lines are kinematically fixed to one another as are the stellar
    models, and the stars and gas independently vary with one another.

    Please see the pPXF documentation for more details on the vast array of
    parameters and options afforded by the software.

    The pPXF software may be downloaded at
    http://www-astro.physics.ox.ac.uk/~mxc/software/

    Parameters
    ----------
    spec : XSpectrum1D
        Spectrum to be fitted
    zgal : float
        Redshift of galaxy
    specresolution : float
        Spectral resolution (R) of the data
    tie_balmer : bool, optional
        Assume intrinsic Balmer decrement.  See documentation in ppxf_util.py,
        as this has implications for the derived reddening.
    limit_doublets : bool, optional
        Limit the ratios of [OII] and [SII] lines to ranges allowed by atomic
        physics.  See documentation in ppxf_util.py, as this has implications
        for getting the true flux values from those reported.
    degree_add : int, optional
        Degree of the additive Legendre polynomial used to modify the template
        continuum in the fit.
    degree_mult : int,optional
    miles_dir: str, optional
      Location of MILES models

    Returns
    -------
    ppfit : ppxf
        Object returned by pPXF; attributes are data pertaining to the fit
    miles : miles
        Contains information about the stellar templates used in the fit.
        See the documentation in miles_util.py for full details
    weights : 1d numpy vector
        Weights of the *stellar* template components.  Equivalent to the
        first N elements of ppfit.weights where N is the number of stellar
        templates used in the fit.
    """

    if spec is None:
        print('Galaxy has no spectrum associated')
        return None

    ### Spectra must be rebinned to a log scale (in wavelength).
    ### pPXF provides a routine to do this, but the input spectra
    ### must be sampled uniformly linear. linetools to the rescue!
    if rebin:
        meddiff = np.median(spec.wavelength.value[1:] -
                            spec.wavelength.value[0:-1])
        newwave = np.arange(spec.wavelength.value[0],
                            spec.wavelength.value[-1], meddiff)
        spec = spec.rebin(newwave * units.AA, do_sig=True, grow_bad_sig=True)

    # get data and transform wavelength to rest frame for template fitting
    wave = spec.wavelength.to(units.Angstrom).value
    flux = spec.flux.value
    flux = flux * (1. + zgal)
    noise = spec.sig.value
    noise = noise * (1. + zgal)
    wave = wave / (1. + zgal)
    # transform to air wavelengths for MILES templates and get approx. FWHM
    wave *= np.median(util.vac_to_air(wave) / wave)

    # use only wavelength range covered by templates
    mask = (wave > 3540) & (wave < 7409)
    #mask = (wave > 1682) & (wave < 10000.)
    maskidx = np.where(mask)[0]
    # also deal with declared good regions of the spectrum
    if 'goodpixels' in kwargs:
        goodpix = kwargs['goodpixels']
        pixmask = np.in1d(maskidx, goodpix)
        newgoodpix = np.where(pixmask)[0]
        kwargs['goodpixels'] = newgoodpix

    wave = wave[mask]
    flux = flux[mask]
    noise = noise[mask]

    # Nonnegative noise values are not allowed
    noise[noise <= 0] = np.max(noise)

    # pPXF requires the spectra to be log rebinned, so do it
    flux, logLam, velscale = util.log_rebin(np.array([wave[0], wave[-1]]),
                                            flux)
    noise, junk1, junk2 = util.log_rebin(np.array([wave[0], wave[-1]]), noise)

    ### The following lines unnecessary for DEIMOS/Hecto spectra due to their
    ### units but rescaling may be necessary for some
    # galaxy = flux / np.median(flux)  # Normalize spectrum to avoid numerical issues
    # print 'Scale flux by', round(np.median(flux),2)
    galaxy = flux  # use the native units

    # pPXF wants the spectral resolution in units of wavelength
    FWHM_gal = wave / specresolution

    ### Set up stellar templates
    #miles_dir = resource_filename('ppxf', '/miles_models/')
    #miles_dir = resource_filename('ppxf', '/emiles_padova_chabrier/')
    if miles_dir is None:
        miles_dir = resource_filename('ppxf', '/miles_padova_chabrier/')
    #path4libcall = miles_dir + 'Mun1.30*.fits'
    #path4libcall = miles_dir + 'Ech1.30*.fits'
    path4libcall = miles_dir + 'Mch1.30*.fits'
    miles = lib.miles(path4libcall, velscale, FWHM_gal, wave_gal=wave)

    ### Stuff for 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.01  # Desired regularization error

    ### Now the emission lines!  Only include lines in fit region.
    if 'goodpixels' in kwargs:
        gal_lam = wave[newgoodpix]
        # Also, log rebinning the spectrum change which pixels are 'goodpixels'
        newnewgoodpix = np.searchsorted(np.exp(logLam), gal_lam, side='left')
        uqnewnewgoodpix = np.unique(newnewgoodpix)
        if uqnewnewgoodpix[-1] == len(wave):
            uqnewnewgoodpix = uqnewnewgoodpix[:-1]
        kwargs['goodpixels'] = uqnewnewgoodpix

    else:
        gal_lam = wave

    def FWHM_func(wave):  # passed to generate emission line templates
        return wave / specresolution

    gas_templates, gas_names, line_wave = util.emission_lines_mask(
        miles.log_lam_temp,
        gal_lam,
        FWHM_func,
        tie_balmer=tie_balmer,
        limit_doublets=limit_doublets)

    # How many gas components do we have?
    balmerlines = [ll for ll in gas_names if ll[0] == 'H']
    numbalm = len(balmerlines)
    numforbid = len(gas_names) - numbalm

    # Stack all templates
    templates = np.column_stack([stars_templates, gas_templates])

    # other needed quantities
    dv = c * (miles.log_lam_temp[0] - logLam[0])
    ### use the following line if not transforming to z=0 first
    # vel = c * np.log(1 + zgal)  # eq.(8) of Cappellari (2017)
    vel = 0.  # We already transformed to the restframe!
    start = [vel, 25.]  # (km/s), starting guess for [V, sigma]

    ### Set up combination of templates
    n_temps = stars_templates.shape[1]
    n_balmer = 1 if tie_balmer else numbalm  # Number of Balmer lines included in the fit
    n_forbidden = numforbid  # Number of other lines included in the fit

    # Assign component=0 to the stellar templates, component=1 to the Balmer
    # emission lines templates, and component=2 to the forbidden lines.
    # component = [0]*n_temps + [1]*n_balmer + [2]*n_forbidden
    component = [0] * n_temps + [1] * (n_balmer + n_forbidden
                                       )  # tie the gas lines together
    gas_component = np.array(
        component) > 0  # gas_component=True for gas templates

    # Fit (V, sig, h3, h4) moments=4 for the stars
    # and (V, sig) moments=2 for the two gas kinematic components
    if len(gas_names) > 0:
        moments = [2, 2]  # fix the gas kinematic components to one another
        start = [[vel, 50.],
                 start]  # Adopt different gas/stars starting values
    else:
        moments = [2]  # only stars to be fit
        start = [vel, 50.]

        # If the Balmer lines are tied one should allow for gas reddening.
    # The gas_reddening can be different from the stellar one, if both are fitted.
    gas_reddening = 0 if tie_balmer else None

    if degree_add is None:
        degree_add = -1
    t = time.time()
    ppfit = ppxf.ppxf(templates,
                      galaxy,
                      noise,
                      velscale,
                      start,
                      plot=False,
                      moments=moments,
                      degree=degree_add,
                      vsyst=dv,
                      lam=np.exp(logLam),
                      clean=False,
                      regul=1. / regul_err,
                      reg_dim=reg_dim,
                      component=component,
                      gas_component=gas_component,
                      gas_names=gas_names,
                      gas_reddening=gas_reddening,
                      mdegree=degree_mult,
                      **kwargs)

    print('Desired Delta Chi^2: %.4g' % np.sqrt(2 * galaxy.size))
    print('Current Delta Chi^2: %.4g' % ((ppfit.chi2 - 1) * galaxy.size))
    print('Elapsed time in PPXF: %.2f s' % (time.time() - t))

    weights = ppfit.weights[
        ~gas_component]  # Exclude weights of the gas templates
    weights = weights.reshape(reg_dim)  # Normalized

    return ppfit, miles, weights