Example #1
0
def test_shapes(backend, moves, nwalkers=32, ndim=3, nsteps=10, seed=1234):
    # Set up the random number generator.
    np.random.seed(seed)

    with backend() as be:
        # Initialize the ensemble, moves and sampler.
        coords = np.random.randn(nwalkers, ndim)
        sampler = EnsembleSampler(nwalkers, ndim, normal_log_prob,
                                  moves=moves, backend=be)

        # Run the sampler.
        sampler.run_mcmc(coords, nsteps)
        chain = sampler.get_chain()
        assert len(chain) == nsteps, "wrong number of steps"

        tau = sampler.get_autocorr_time(quiet=True)
        assert tau.shape == (ndim,)

        # Check the shapes.
        assert sampler.chain.shape == (nwalkers, nsteps, ndim), \
            "incorrect coordinate dimensions"
        assert sampler.get_chain().shape == (nsteps, nwalkers, ndim), \
            "incorrect coordinate dimensions"
        assert sampler.lnprobability.shape == (nsteps, nwalkers), \
            "incorrect probability dimensions"

        assert sampler.acceptance_fraction.shape == (nwalkers,), \
            "incorrect acceptance fraction dimensions"

        # Check the shape of the flattened coords.
        assert sampler.get_chain(flat=True).shape == \
            (nsteps * nwalkers, ndim), "incorrect coordinate dimensions"
        assert sampler.get_log_prob(flat=True).shape == \
            (nsteps*nwalkers,), "incorrect probability dimensions"
Example #2
0
def test_shapes(backend, moves, nwalkers=32, ndim=3, nsteps=10, seed=1234):
    # Set up the random number generator.
    np.random.seed(seed)

    with backend() as be:
        # Initialize the ensemble, moves and sampler.
        coords = np.random.randn(nwalkers, ndim)
        sampler = EnsembleSampler(nwalkers,
                                  ndim,
                                  normal_log_prob,
                                  moves=moves,
                                  backend=be)

        # Run the sampler.
        sampler.run_mcmc(coords, nsteps)

        chain = sampler.get_chain()
        assert len(chain) == nsteps, "wrong number of steps"

        tau = sampler.get_autocorr_time(quiet=True)
        assert tau.shape == (ndim, )

        # Check the shapes.
        with pytest.warns(DeprecationWarning):
            assert sampler.chain.shape == (
                nwalkers,
                nsteps,
                ndim,
            ), "incorrect coordinate dimensions"
        with pytest.warns(DeprecationWarning):
            assert sampler.lnprobability.shape == (
                nwalkers,
                nsteps,
            ), "incorrect probability dimensions"
        assert sampler.get_chain().shape == (
            nsteps,
            nwalkers,
            ndim,
        ), "incorrect coordinate dimensions"
        assert sampler.get_log_prob().shape == (
            nsteps,
            nwalkers,
        ), "incorrect probability dimensions"

        assert sampler.acceptance_fraction.shape == (
            nwalkers, ), "incorrect acceptance fraction dimensions"

        # Check the shape of the flattened coords.
        assert sampler.get_chain(flat=True).shape == (
            nsteps * nwalkers,
            ndim,
        ), "incorrect coordinate dimensions"
        assert sampler.get_log_prob(flat=True).shape == (
            nsteps * nwalkers, ), "incorrect probability dimensions"
Example #3
0
def test_sampler_generator():
    nwalkers = 32
    ndim = 3
    nsteps = 5
    np.random.seed(456)
    coords = np.random.randn(nwalkers, ndim)
    seed1 = np.random.default_rng(1)
    sampler1 = EnsembleSampler(nwalkers, ndim, normal_log_prob, seed=seed1)
    sampler1.run_mcmc(coords, nsteps)
    seed2 = np.random.default_rng(1)
    sampler2 = EnsembleSampler(nwalkers, ndim, normal_log_prob, seed=seed2)
    sampler2.run_mcmc(coords, nsteps)
    np.testing.assert_allclose(sampler1.get_chain(), sampler2.get_chain())
    np.testing.assert_allclose(sampler1.get_log_prob(),
                               sampler2.get_log_prob())
Example #4
0
def test_errors(backend, nwalkers=32, ndim=3, nsteps=5, seed=1234):
    # Set up the random number generator.
    np.random.seed(seed)

    with backend() as be:
        # Initialize the ensemble, proposal, and sampler.
        coords = np.random.randn(nwalkers, ndim)
        sampler = EnsembleSampler(nwalkers, ndim, normal_log_prob, backend=be)

        # Test for not running.
        with pytest.raises(AttributeError):
            sampler.get_chain()
        with pytest.raises(AttributeError):
            sampler.get_log_prob()

        # What about not storing the chain.
        sampler.run_mcmc(coords, nsteps, store=False)
        with pytest.raises(AttributeError):
            sampler.get_chain()

        # Now what about if we try to continue using the sampler with an
        # ensemble of a different shape.
        sampler.run_mcmc(coords, nsteps, store=False)

        coords2 = np.random.randn(nwalkers, ndim + 1)
        with pytest.raises(ValueError):
            list(sampler.run_mcmc(coords2, nsteps))

        # Ensure that a warning is logged if the inital coords don't allow
        # the chain to explore all of parameter space, and that one is not
        # if we explicitly disable it, or the initial coords can.
        with pytest.warns(RuntimeWarning) as recorded_warnings:
            sampler.run_mcmc(np.ones((nwalkers, ndim)), nsteps)
            assert len(recorded_warnings) == 1
        with pytest.warns(None) as recorded_warnings:
            sampler.run_mcmc(
                np.ones((nwalkers, ndim)),
                nsteps,
                skip_initial_state_check=True,
            )
            sampler.run_mcmc(np.random.randn(nwalkers, ndim), nsteps)
            assert len(recorded_warnings) == 0
Example #5
0
def test_vectorize():
    def lp_vec(p):
        return -0.5 * np.sum(p**2, axis=1)

    np.random.seed(42)
    nwalkers, ndim = 32, 3
    coords = np.random.randn(nwalkers, ndim)
    sampler = EnsembleSampler(nwalkers, ndim, lp_vec, vectorize=True)
    sampler.run_mcmc(coords, 10)

    assert sampler.get_chain().shape == (10, nwalkers, ndim)
Example #6
0
def test_vectorize():
    def lp_vec(p):
        return -0.5 * np.sum(p**2, axis=1)

    np.random.seed(42)
    nwalkers, ndim = 32, 3
    coords = np.random.randn(nwalkers, ndim)
    sampler = EnsembleSampler(nwalkers, ndim, lp_vec, vectorize=True)
    sampler.run_mcmc(coords, 10)

    assert sampler.get_chain().shape == (10, nwalkers, ndim)
Example #7
0
class Sampler:
    """
    wrapper of emcee.EnsembleSampler. 
    """
    def __init__(self, lnpost, p0, nwalkers=120, blobs_dtype=float):
        """
        init
        """

        self.lnpost = lnpost
        blobs_dtype = blobs_dtype  # Note: Here dtype must be specified, otherwise an error happens. #[("lnlike",float),]
        self.sampler = EnsembleSampler(
            nwalkers, p0.shape[1], lnpost, blobs_dtype=blobs_dtype
        )  # NOTE: dtype must be list of tuple (not tuple of tuple)
        self.p0 = p0
        self.p_last = p0
        self.ndim = p0.shape[1]

    def reset_sampler(self):
        self.sampler.reset()

    def sample(self, n_sample, burnin=False, use_pool=False):
        """
        execute mcmc for given iteration steps.
        """
        desc = "burnin" if burnin else "sample"

        with Pool() as pool:
            self.sampler.pool = pool if use_pool else None
            iteration = tqdm(self.sampler.sample(self.p_last,
                                                 iterations=n_sample),
                             total=n_sample,
                             desc=desc)
            for _ret in iteration:
                self.p_last = _ret.coords  # if uses_emcee3 else _ret[0]  # for emcee2
                lnposts = _ret.log_prob  # if uses_emcee3 else _ret[1]  # for emcee2
                iteration.set_postfix(lnpost_min=np.min(lnposts),
                                      lnpost_max=np.max(lnposts),
                                      lnpost_mean=np.mean(lnposts))
            if burnin:
                self.reset_sampler()

    def get_chain(self, **kwargs):
        return self.sampler.get_chain(**kwargs)

    def get_log_prob(self, **kwargs):
        return self.sampler.get_log_prob(**kwargs)

    def get_blobs(self, **kwargs):
        return self.sampler.get_blobs(**kwargs)

    def get_last_sample(self, **kwargs):
        return self.sampler.get_last_sample(**kwargs)

    def _save(self, fname_base):
        np.save(fname_base + "_chain.npy", self.get_chain())
        np.save(fname_base + "_lnprob.npy", self.get_log_prob())
        np.save(fname_base + "_lnlike.npy", self.get_blobs())

    def save(self, fname_base):
        '''
        Save MCMC results into "<fname_base>_chain/lnprob/lnlike.npy".
        If fname_base is like "your_directory/your_prefix", create "your_directory" before saving.
        '''
        dirname = os.path.dirname(fname_base)
        if dirname == "":
            self._save(fname_base)
        else:
            if not os.path.isdir(dirname): os.mkdir(dirname)
            self._save(fname_base)

    def save_pickle(self, fname_base, overwrite=False):
        fname = fname_base + '_.gz'
        if os.path.exists(fname):
            if overwrite:
                warn(f"{fname} exsits already. It will be overwritten.")
            else:
                raise RuntimeError(
                    f"{fname} exsits already. If you want to overwrite it, set \"overwrite=True\"."
                )
        data = pickle.dumps(self)
        with gzip.open(fname, mode='wb') as fp:
            fp.write(data)
Example #8
0
def runSEDphotFit(lambdaObs, fluxObs, efluxObs, \
      filters, \
      z = 0.01, \
      UL = [], \
      S9p7 = -99.,\
      ExtCurve = 'iragnsep', \
      Nmc = 10000, pgrbar = 1, \
      NoSiem = False, \
      Pdust = [10., 1.], PPAH = [9., 1.], PnormAGN = [10., 1.], PSiEm = [10., 1.], \
      templ = '', \
      NOAGN = False):
    """
    This function fits the observed photometric SED.
    ------------
    :param lambdaObs: observed wavelengths (in microns).
    :param fluxObs: observed fluxes (in Jansky).
    :param efluxObs: observed uncertainties on the fluxes (in Jansky).
    :param filters: name of the photometric filters to include in the fit.
    ------------
    :keyword z: redshift of the source. Default = 0.01.
    :keyword UL: vector of length Nphot, where Nphot is the number of photometric data. If any of the value is set to 1, 
    the corresponding flux is set has an upper limit in the fit. Default = [].
    :keyword S9p7: can be used to pass a fixed value for the total silicate absorption at 9.7 micron. Default = -99.
    :keyword ExtCurve: pass the name of the extinction curve to use. Default = 'iragnsep'.
    :keyword Nmc: numer of MCMC run. Default = 10000.
    :keyword pgrbar: if set to 1, display a progress bar while fitting the SED. Default = 1.
	:keyword NoSiem: if set to True, no silicate emission template is included in the fit. Default = False.
    :keyword Pdust: normal prior on the log-normalisation of the galaxy dust continuum template. Default = [10., 1.] ([mean, std dev]).
    :keyword PPAH: normal prior on the log-normalisation of the PAH template. Default = [9., 1.] ([mean, std dev]).
    :keyword PnormAGN: normal prior on the log-normalisation of the AGN template. Default = [10., 1.] ([mean, std dev]).
    :keyword PSiem: normal prior on the log-normalisation of the silicate emission template. Default = [10., 1.] ([mean, std dev]).
    :keyword templ: the templates used for the fits. Default: iragnsep templates.
    :keyword NOAGN: if set to True, fits are ran with SF templates only (i.e. no AGN emission is accounted for). Default = False.
	------------
    :return dfRes: dataframe containing the results of all the possible fits.
    """

    path_iragnsep = os.path.dirname(iragnsep.__file__)

    # If no templates are passed, open the Bernhard+20 templates.
    if len(templ) == 0:
        templ = pd.read_csv(path_iragnsep + '/iragnsep_templ.csv')

    # Extract the name of the templates
    keys = templ.keys().values
    nameTempl_gal = []
    nameTempl_PAH = []
    nameTempl_AGN = []
    nameTempl_Siem = []
    for key in keys:
        if str(key).startswith('gal'):
            if str(key).endswith('PAH') == False:
                nameTempl_gal.append(key)
            else:
                nameTempl_PAH.append(key)
        if str(key).startswith('AGN'):
            if str(key).endswith('Siem'):
                nameTempl_Siem.append(key)
            else:
                nameTempl_AGN.append(key)

    # Test that we have template for everything (if no galaxy then it crashes)
    if len(nameTempl_gal) == 0:
        raise ValueError(
            'The galaxy template does not exist. The name of the column defining nuLnu for the galaxy template needs to start with "gal".'
        )

    # define the wavelengths
    try:
        wavTempl = templ['lambda_mic'].values
    except:
        raise ValueError(
            'Rename the wavelengths column of the template "lambda_mic".')

    # Increase the uncertainties if too small. Otherwise walkers get lost.
    SNR = efluxObs / fluxObs
    efluxObs[SNR < 1e-2] = fluxObs[SNR < 1e-2] * 1e-2

    # Open the extinction curve
    EC_wav, EC_tau = getExtCurve(ExtCurve)
    EC_wav_AGN, EC_tau_AGN = getExtCurve('PAHfit')
    if S9p7 > 0.:
        #Calculate tau9p7 for the galaxy
        tau9p7_gal = S9p7toTau9p7(S9p7, 'gal')
        tau9p7_AGN = S9p7toTau9p7(S9p7, 'AGN')

        # Galaxy
        tau = np.interp(lambdaObs / (1. + z), EC_wav, EC_tau) * tau9p7_gal
        obsPerWav_gal = ((1. - np.exp(-tau)) / tau)

        #AGN
        tau = np.interp(lambdaObs /
                        (1. + z), EC_wav_AGN, EC_tau_AGN) * tau9p7_AGN
        obsPerWav_AGN = np.exp(-tau)
    else:
        obsPerWav_gal = 1.
        obsPerWav_AGN = 1.

    # Define a vectors of zeros if no upper limts are passed.
    if len(UL) != len(lambdaObs):
        UL = np.zeros(len(lambdaObs))

    o = np.where(UL == 0.)[0]
    if len(o) == 0.:
        UL = np.zeros(len(lambdaObs))
        efluxObs = fluxObs * 0.1

    # Define the free parameters
    # Norm AGN continuum
    lnAGN_perTempl = []
    elnAGN_perTempl = []

    # Norm silicate emission
    lnSi_perTempl = []
    elnSi_perTempl = []

    # Norm dust continuum
    lnDust_perTempl = []
    elnDust_perTempl = []

    # Norm PAHs
    lnPAH_perTempl = []
    elnPAH_perTempl = []

    # final loglikelihood of the model
    logl_perTempl = []

    # name of the AGN and galaxy template
    tplNameGal_perTempl = []
    tplNameAGN_perTempl = []

    # if AGN is accounted for (1) or not (0)
    AGNon = []

    # Number of parameters in the model
    nParms = []

    # We loop over the 6 galaxy templates, first fitting only galaxy templates and then including the AGN templates.
    for name_i in nameTempl_gal:
        # if name_i != 'gal7_dust':
        # 	continue
        assert isinstance(name_i, str), "The list nameTempl requests strings as it corresponds to the names" + \
                " given to the various templates of galaxies to use for the fit."

        if pgrbar == 1:
            print("****************************************")
            print("Fit of " + name_i + " as galaxy template")

        # Define synthetic fluxes for the dust continuum model at the observed wavelength to match the observed fluxes
        nuLnuBGTempl = templ[name_i].values

        SEDgen = modelToSED(wavTempl, nuLnuBGTempl, z)
        fluxPhot_model = []
        for filt in filters:
            fluxPhot_model.append(getattr(SEDgen, filt)())

        modelDust = np.array(fluxPhot_model) * obsPerWav_gal

        # Define synthetic fluxes for the PAH model at the observed wavelength to match the observed fluxes.
        # When an empirical template is used, define a vector of zeros so that no PAH emission is accounted for.
        nuLnuSGTempl = templ[nameTempl_PAH[0]].values

        SEDgen = modelToSED(wavTempl, nuLnuSGTempl, z)
        fluxPhot_model = []
        for filt in filters:
            fluxPhot_model.append(getattr(SEDgen, filt)())

        modelPAH = np.array(fluxPhot_model)

        # Perform the fit without the AGN contribution
        ndim = 2  # Number of free params
        nwalkers = int(10. * ndim)  # Number of walkers

        # Define the parameters as flat distributions between -3. and 3. (We normalised to zero each parameters to ease convergence).
        parms = np.zeros(shape=(nwalkers, ndim))
        parms[:, 0] = np.random.uniform(low=-1. * Pdust[1],
                                        high=Pdust[1],
                                        size=nwalkers)  # norm Dust
        parms[:, 1] = np.random.uniform(low=-1. * PPAH[1],
                                        high=PPAH[1],
                                        size=nwalkers)  # norm PAH

        # Set the ensemble sampler of Goodman&Weare and run the MCMC for Nmc steps
        if pgrbar == 1:
            print(" -- NO AGN --")
        sampler = EnsembleSampler(nwalkers, ndim, lnpostfn_photo_noAGN, \
                moves=[emcee.moves.StretchMove()], \
                args = (np.array([Pdust, PPAH]), modelDust, modelPAH, UL, fluxObs, efluxObs))
        sampler.run_mcmc(parms, int(Nmc), progress=bool(pgrbar))

        # Build the flat chain, after burning 20% of the chain and thinning to every 10 values.
        NburnIn = int(0.2 * Nmc)
        chain = sampler.get_chain(discard=NburnIn, thin=10, flat=True)

        # Save the best fit parameters. Median of the posterior is taken as the best fit parameter and the standard deviation as 1sigma uncertainties.
        # Norm dust continuum
        lnDust_perTempl.append(np.median(chain[:, 0]))
        elnDust_perTempl.append(np.std(chain[:, 0]))

        # Norm PAH emission (or -20. when the empirical template is used).
        lnPAH_perTempl.append(np.median(chain[:, 1]))
        elnPAH_perTempl.append(np.std(chain[:, 1]))

        # Calulate the final loglikelihood of the model, using the best fit parameters.
        logl_perTempl.append(
            lnpostfn_photo_noAGN(
                np.array([lnDust_perTempl[-1], lnPAH_perTempl[-1]]),
                np.array([Pdust, PPAH]), modelDust, modelPAH, UL, fluxObs,
                efluxObs))

        # Norm on the silicate emission. -99. here since no AGN is accounted for.
        lnSi_perTempl.append(-99.)
        elnSi_perTempl.append(-99.)

        # Norm on the AGN template. -99. here since no AGN is accounted for.
        lnAGN_perTempl.append(-99.)
        elnAGN_perTempl.append(-99.)

        # No AGN in this fits, so AGNon set to zero
        AGNon.append(0.)

        # Save the numbers of parameters used in the fit, i.e. 2.
        nParms.append(2.)

        # Save the name of the template for the galaxy.
        tplNameGal_perTempl.append(name_i)

        # No AGN templates used in this fit, so name of the AGN template is set to 'N/A'.
        tplNameAGN_perTempl.append('N/A')

        if NOAGN != True:

            # Fit including the AGN. Loop over the two templates AGN A and AGN B.
            for AGN_i in nameTempl_AGN:

                # calculate the synthetic photometry of the AGN template at wavelengths lambdaObs.
                nuLnu_AGN = templ[AGN_i].values
                # generate the photometry
                SEDgen = modelToSED(wavTempl, nuLnu_AGN, z)
                modelAGN = []
                for filt in filters:
                    modelAGN.append(getattr(SEDgen, filt)())

                modelAGN = np.array(modelAGN) * obsPerWav_AGN

                # calculate the synthetic photometry of the silicate template at wavelengths lambdaObs.
                # If no silicate emission is considered, set a vector of zeros so that no silicate emisison is account for.
                if NoSiem == False:
                    modelSiem = []
                    nuLnu_Siem = templ[nameTempl_Siem].values.flatten()
                    SEDgen = modelToSED(wavTempl, nuLnu_Siem, z)
                    for filt in filters:
                        modelSiem.append(getattr(SEDgen, filt)())

                    modelSiem = np.array(modelSiem) * obsPerWav_AGN
                else:
                    modelSiem = modelAGN * 0.

                # Perform the fit with the AGN contribution
                ndim = 4  # Number of parmameters
                nwalkers = int(10. * ndim)  # Number of walkers

                # Define the starting parameters as flat distributions between -1 and 1. We normalised each parameter to zero to each convergence.
                parms = np.zeros(shape=(nwalkers, ndim))
                parms[:, 0] = np.random.uniform(low=-1. * Pdust[1],
                                                high=Pdust[1],
                                                size=nwalkers)  # norm Dust
                parms[:, 1] = np.random.uniform(low=-1. * PPAH[1],
                                                high=PPAH[1],
                                                size=nwalkers)  # norm PAH
                parms[:, 2] = np.random.uniform(low=-1. * PnormAGN[1],
                                                high=PnormAGN[1],
                                                size=nwalkers)  # normAGN
                parms[:, 3] = np.random.uniform(low=-1. * PSiEm[1],
                                                high=PSiEm[1],
                                                size=nwalkers)  # normSi

                # Set the ensemble sampler of Goodman&Weare and run the MCMC for Nmc steps
                print('-- ' + AGN_i + ' --')
                sampler = EnsembleSampler(nwalkers, ndim, lnpostfn_photo_wAGN, \
                        moves=[emcee.moves.StretchMove()],\
                        args = (np.array([Pdust, PPAH, PnormAGN, PSiEm]), \
                        modelDust, modelPAH, modelAGN, modelSiem, UL, fluxObs, efluxObs))
                sampler.run_mcmc(parms, int(Nmc), progress=bool(pgrbar))

                # Build the flat chain, after burning 20% of the chain and thinning to every 10 values in the chain.
                chain = sampler.get_chain(discard=NburnIn, thin=10, flat=True)

                # Save the best fit parameters. Median of the posterior is taken as the best fit parameter and the standard
                # deviation as 1sigma uncertainties.
                # Dust Normalisation
                lnDust_perTempl.append(np.median(chain[:, 0]))
                elnDust_perTempl.append(np.std(chain[:, 0]))

                # PAH normalisation
                lnPAH_perTempl.append(np.median(chain[:, 1]))
                elnPAH_perTempl.append(np.std(chain[:, 1]))

                # AGN continuum Norm
                lnAGN_perTempl.append(np.median(chain[:, 2]))
                elnAGN_perTempl.append(np.std(chain[:, 2]))

                if NoSiem == True:

                    # Si emission Norm
                    lnSi_perTempl.append(-99.)
                    elnSi_perTempl.append(-99.)

                    # Numbers of params in the model
                    nParms.append(3.)

                else:

                    # Si emission Norm
                    lnSi_perTempl.append(np.median(chain[:, 3]))
                    elnSi_perTempl.append(np.std(chain[:, 3]))

                    # Numbers of params in the model
                    nParms.append(4.)

                # AGN accounted for in this case, so AGNon = 1
                AGNon.append(1.)

                # Name of the galaxy template
                tplNameGal_perTempl.append(name_i)

                # Name of the AGN template
                tplNameAGN_perTempl.append(AGN_i)

                # loglikelihood of the model
                logl_perTempl.append(lnpostfn_photo_wAGN(np.array([lnDust_perTempl[-1], lnPAH_perTempl[-1], lnAGN_perTempl[-1], \
                         lnSi_perTempl[-1]]), np.array([Pdust, PPAH, PnormAGN, PSiEm]), modelDust, modelPAH, modelAGN, \
                         modelSiem, UL, fluxObs, efluxObs))

                if NoSiem == True:
                    lnSi_perTempl[-1] = -99.

    # Find the best model and the Akaike weight amongst all the 18 possible fits by comparing their final loglikelihood
    bestModelInd, Awi = exctractBestModel(logl_perTempl,
                                          nParms,
                                          len(lambdaObs),
                                          corrected=True)
    bestModelFlag = np.zeros(len(AGNon))
    bestModelFlag[bestModelInd] = 1

    # Save the results in a table
    resDict = {'logNormGal_dust': np.array(lnDust_perTempl) + Pdust[0], 'elogNormGal_dust': np.array(elnDust_perTempl), \
         'logNormGal_PAH': np.array(lnPAH_perTempl) + PPAH[0], 'elogNormGal_PAH': np.array(elnPAH_perTempl), \
         'logNormAGN': np.array(lnAGN_perTempl) + PnormAGN[0], 'elogNormAGN': np.array(elnAGN_perTempl), \
         'logNormSiem': np.array(lnSi_perTempl) + PSiEm[0], 'elogNormSiem': np.array(elnSi_perTempl), \
         'logl': logl_perTempl, 'AGNon': AGNon, 'tplName_gal': tplNameGal_perTempl, 'tplName_AGN': tplNameAGN_perTempl,\
         'bestModelFlag': bestModelFlag, 'Aw': Awi, 'S9p7':S9p7}

    dfRes = pd.DataFrame(resDict)

    return dfRes
Example #9
0
def runSEDspecFit(wavSpec, fluxSpec, efluxSpec,\
      wavPhot, fluxPhot, efluxPhot, \
      filters, \
      z = -0.01,\
      ULPhot = [], \
      obsCorr = True,\
      S9p7_fixed = -99., \
      ExtCurve = 'iragnsep',\
      Nmc = 10000, pgrbar = 1, \
      Pdust = [11., 3.], PPAH = [9.7, 3.], \
      PPL = [-1., 3.], Palpha = [0., 1.], \
      Pbreak = [40., 10.], \
      PSi = [-1., 3.], \
      templ = ''):
    """
    This function fits the observed SED when a spectrum is combined to photometric data. The observed wavelengths, 
    fluxes and uncertainties on the fluxes are passed separately for the spectrum and the photometric data.
    ------------
    :param wavSpec: observed wavelengths for the spectrum (in microns).
    :param fluxSpec: observed fluxes for the spectrum (in Jansky).
    :param efluxSpec: observed uncertainties on the fluxes for the spectrum (in Jansky).
    :param wavPhot: observed wavelengths for the photometry (in microns).
    :param fluxPhot: observed fluxes for the photometry (in Jansky).
    :param efluxPhot: observed uncertainties on the fluxes for the photometry (in Jansky).
    :param filters: name of the photometric filters to include in the fit.
    ------------
    :keyword z: redshift of the source. Default = 0.01.
    :keyword ULPhot: vector of length Nphot, where Nphot is the number of photometric data. If any of the value is set to 1, 
    the corresponding flux is set has an upper limit in the fit. Default = [].
    :keyword obsCorr: if set to True, iragnsep attempt to calculate the total silicate absorption at 9.7micron, 
    and to correct the observed fluxes for obscuration. Default = True
    :keyword S9p7_fixed: can be used to pass a fixed value for the total silicate absorption at 9.7 micron. Default = -99.
    :keyword ExtCurve: pass the name of the extinction curve to use. Default = 'iragnsep'.
    :keyword Nmc: numer of MCMC run. Default = 10000.
    :keyword pgrbar: if set to 1, display a progress bar while fitting the SED. Default = 1.
    :keyword Pdust: normal prior on the log-normalisation of the galaxy dust continuum template. Default = [11., 3.] ([mean, std dev]).
    :keyword PPAH: normal prior on the log-normalisation of the PAH template. Default = [9.7, 3.] ([mean, std dev]).
	:keyword Ppl: normal prior on the log-normalisation AGN continuum (defined at 10 micron). Default = [-1., 3.] ([mean, std dev]).
	:keyword Palpha: normal prior on the three slopes alpha of the AGN continuum model. Default = [0., 1.] ([mean, std dev]).
	:keyword Pbreak: prior on lbreak, the position of the break. Default = [40., 10.] ([mean, std dev]).
	:keyword PSi: prior on silicate emission. Default = [-1., 3.] ([mean, std dev]).
	:keyword templ: the templates used for the fits. Default: iragnsep templates.
	------------
    :return res_fit: dataframe containing the results of all the possible fits.
    :return res_fitBM: dataframe containing the results of the best fit only.
    """

    path_iragnsep = os.path.dirname(iragnsep.__file__)

    # Extract the names of the templates
    keys = templ.keys().values
    nameTempl_gal = []
    nameTempl_PAH = []
    for key in keys:
        if str(key).startswith('gal'):
            if str(key).endswith('PAH') == False:
                nameTempl_gal.append(key)
            else:
                nameTempl_PAH.append(key)

    # Test that we have template for everything (if no galaxy then it crashes)
    if len(nameTempl_gal) == 0:
        raise ValueError(
            'The galaxy template does not exist. The name of the column defining nuLnu for the galaxy template needs to start with "gal".'
        )

    # define the wavelengths
    try:
        wavTempl = templ['lambda_mic'].values
    except:
        raise ValueError(
            'Rename the wavelengths column of the template "lambda_mic".')

    # Define the rest wavelengths for the photometric and the spectral data
    wavSpec_rest = wavSpec / (1. + z)
    wavPhot_rest = wavPhot / (1. + z)

    # Prepare the upper limits. Set a vector of zeros for the spectra. Set a vector fo zeros for the photometry if UL is underfined.
    if len(ULPhot) != len(wavPhot):
        ULPhot = np.zeros(len(wavPhot))
    ULSpec = np.zeros(len(wavSpec))
    UL = np.concatenate([ULSpec, ULPhot])

    # Concatenate the spectral and the photometric data.
    wavFit = np.concatenate([wavSpec, wavPhot])
    fluxFit = np.concatenate([fluxSpec, fluxPhot])
    efluxFit = np.concatenate([efluxSpec, efluxPhot])

    # Calculate the weights
    e_av_phot = np.mean(efluxPhot[ULPhot == 0.] / fluxPhot[ULPhot == 0.])
    e_av_spec = np.mean(efluxSpec[ULSpec == 0.] / fluxSpec[ULSpec == 0.])
    e_av_rat = e_av_spec / e_av_phot

    xrat = [1., 2., 4., 6.]
    yrat = [1., 7., 19., 30.]
    fit = np.polyfit(xrat, yrat, 1)

    multFact = max((fit[0] * e_av_rat + fit[1]), 1.)

    Specwei = np.zeros(len(wavSpec)) + (multFact) / len(wavSpec)
    Photwei = np.ones(len(wavPhot))
    wei = np.concatenate([Specwei, Photwei])

    xe = [0.011, 0.017, 0.034, 0.069]
    ye = [0.5, 1., 5., 15.]
    fit = np.polyfit(xe, ye, 2)

    wei *= max(fit[0] * e_av_phot**2. + fit[1] * e_av_phot + fit[2], 1.)

    # Correct for absorption, using the total 9.7micron absorption feature.
    if S9p7_fixed == -99.:
        # Test if there are actually data in the wavelength range, if not, continue the fit without correcting for obscuration
        o = np.where((wavSpec_rest > 9.) & (wavSpec_rest < 10.))[0]
        if (len(o) == 0.) & (obsCorr == True):
            print('*******************')
            print('It has failed to correct for obscuration. There is no data in the range required to correct for obscuration.' + \
               ' The fit is continued without correcting for obscuration.')
            print('*******************')
            obsCorr = False

        # Correct for obscuration. If it somehow fails, continue the fit without correcting for obscuration.
        if obsCorr == True:
            try:
                S9p7 = calc_S9p7(wavSpec_rest, fluxSpec)
            except:
                S9p7 = -99.
                print('*******************')
                print('It has failed to correct the IRS spectrum for obscuration. The most likely explanation is '+ \
                   'redshift since it needs the restframe anchor ' + \
                   'wavelengths to measure the strength of the silicate absorption. The fit is continued without correcting for obscuration.')
                print('*******************')
                pass
        else:
            S9p7 = -99.
    else:
        S9p7 = S9p7_fixed

    # Open the extincrtion curve
    EC_wav, EC_tau = getExtCurve(ExtCurve)
    EC_wav_AGN, EC_tau_AGN = getExtCurve('PAHfit')
    if S9p7 > 0.:
        #Calculate tau9p7 for the galaxy
        tau9p7_gal = S9p7toTau9p7(S9p7, 'gal')
        tau9p7_AGN = S9p7toTau9p7(S9p7, 'AGN')

        # Galaxy
        tau = np.interp(wavFit / (1. + z), EC_wav, EC_tau) * tau9p7_gal
        obsPerWav_gal = ((1. - np.exp(-tau)) / tau)

        #AGN
        tau = np.interp(wavFit / (1. + z), EC_wav_AGN, EC_tau_AGN) * tau9p7_AGN
        obsPerWav_AGN = np.exp(-tau)
    else:
        obsPerWav_gal = 1.
        obsPerWav_AGN = 1.

    # Define the free parameters
    logNorm_Dust_perTempl = []  #dust continuum
    elogNorm_Dust_perTempl = []
    logNorm_PAH_perTempl = []  #PAH emission
    elogNorm_PAH_perTempl = []

    logNorm_Si_perTempl = []  #silicate at 18 micron
    elogNorm_Si_perTempl = []
    logNormAGN_PL_perTempl = []  #norm AGN continuum
    elogNormAGN_PL_perTempl = []
    l_break_PL_perTempl = []  # position of the break for the PL
    el_break_PL_perTempl = []
    alpha1_perTempl = []  #slope of the first power law
    ealpha1_perTempl = []
    alpha2_perTempl = []  #slope of the second power law
    ealpha2_perTempl = []
    alpha3_perTempl = []
    ealpha3_perTempl = []
    dSi_perTempl = []
    edSi_perTempl = []

    logl_perTempl = []  #log-likelihood of the mode
    tplName_perTempl = []  #Name of the template
    S9p7_save = []  #total absorption at 9.7 micron
    AGNon = []  #flag for the use of the AGN in the fit
    nParms = []  # number of parameters

    x_red = 10**np.arange(np.log10(7), np.log10(30), 0.05)
    x_red[0] = wavFit.min() / (1. + z)

    # Fit looping over every of our galaxy templates
    for name_i in nameTempl_gal:
        assert isinstance(name_i, str), "The list nameTempl requests strings as it corresponds to the names" + \
                " given to the various templates of galaxies to use for the fit."

        if pgrbar == 1:
            print("****************************************")
            print("  Fit of " + name_i + " as galaxy template  ")
            print("****************************************")

        # Define synthetic fluxes for the dust continuum model at the observed wavelength to match the observed fluxes
        nuLnuBGTempl = templ[name_i].values

        Fnu = nuLnuToFnu(wavTempl, nuLnuBGTempl, z)
        fluxSpec_model = np.interp(wavSpec_rest, wavTempl, Fnu)

        SEDgen = modelToSED(wavTempl, nuLnuBGTempl, z)
        fluxPhot_model = []
        for filt in filters:
            fluxPhot_model.append(getattr(SEDgen, filt)())

        modelDust = np.concatenate([fluxSpec_model, fluxPhot_model
                                    ]) * obsPerWav_gal

        # Define synthetic fluxes for the PAH model at the observed wavelength to match the observed fluxes.
        # When an empirical template is used, define a vector of zeros so that no PAH emission is accounted for.
        nuLnuSGTempl = templ[nameTempl_PAH[0]].values

        Fnu = nuLnuToFnu(wavTempl, nuLnuSGTempl, z)
        fluxSpec_model = np.interp(wavSpec_rest, wavTempl, Fnu)

        SEDgen = modelToSED(wavTempl, nuLnuSGTempl, z)
        fluxPhot_model = []
        for filt in filters:
            fluxPhot_model.append(getattr(SEDgen, filt)())

        modelPAH = np.concatenate([fluxSpec_model, fluxPhot_model])

        # fit without the AGN
        ndim = 2  #Number of paraneter
        nwalkers = int(2. * ndim)  #number of walkers

        # Define the starting point of each of the parameters. Flat distrib. between -1 and 1. Parameters are normalised with a zero mean to ease convergence.
        parms = np.zeros(shape=(nwalkers, ndim))
        parms[:, 0] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # norm Dust
        parms[:, 1] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # norm PAH

        # Set the ensemble sampler of Goodman&Weare and run the MCMC for Nmc steps.
        sampler = EnsembleSampler(nwalkers, ndim, lnpostfn_spec_noAGN, \
                args = (np.array([Pdust, PPAH]), modelDust, modelPAH, fluxFit, efluxFit, \
                UL, wei))
        sampler.run_mcmc(parms, Nmc, progress=bool(pgrbar))

        # Build the flat chain after burning 20% of it and thinning to every 10 values.
        NburnIn = int(0.2 * Nmc)
        chain = sampler.get_chain(discard=NburnIn, thin=10, flat=True)
        dfChain = pd.DataFrame(chain)
        dfChain.columns = ['logNormDust', 'logNormPAH']

        # Save the optimised parameters. Median of the posterior as best fitting value and std dev as 1sigma uncertainties.
        # Dust continuum
        logNorm_Dust_perTempl.append(dfChain['logNormDust'].median())
        elogNorm_Dust_perTempl.append(dfChain['logNormDust'].std())

        # PAH
        logNorm_PAH_perTempl.append(dfChain['logNormPAH'].median())
        elogNorm_PAH_perTempl.append(dfChain['logNormPAH'].std())

        # Norm AGN, here -99. since no AGN accounted for
        logNormAGN_PL_perTempl.append(-99.)
        elogNormAGN_PL_perTempl.append(-99.)

        # slope of the first power law, here -99. since no AGN accounted for
        alpha1_perTempl.append(-99.)
        ealpha1_perTempl.append(-99.)

        # slope of the second power law, here -99. since no AGN accounted for
        alpha2_perTempl.append(-99.)
        ealpha2_perTempl.append(-99.)

        alpha3_perTempl.append(-99.)
        ealpha3_perTempl.append(-99.)

        # cut off or position of the break, here -99. since no AGN accounted for
        l_break_PL_perTempl.append(-99.)
        el_break_PL_perTempl.append(-99.)

        # silicate emisison at 10 microns, here -99. since no AGN accounted for
        logNorm_Si_perTempl.append(-99.)
        elogNorm_Si_perTempl.append(-99.)

        # Shift of the peak for the siicate emission
        dSi_perTempl.append(-99.)
        edSi_perTempl.append(-99.)

        # Calculate the logl of the model
        logl = lnpostfn_spec_noAGN(np.array([logNorm_Dust_perTempl[-1], logNorm_PAH_perTempl[-1]]), \
                 np.array([Pdust, PPAH]), \
                 modelDust, modelPAH, \
                 fluxFit, efluxFit, \
                 UL, wei)

        logl_perTempl.append(logl)

        # Flag for the use of an AGN. Here 0., since no AGN is accounted for.
        AGNon.append(0.)

        # save the number of parameters
        nParms.append(ndim)

        # save the name of the template
        tplName_perTempl.append(name_i)

        # Save teh total absorption at 9.7 microns.
        S9p7_save.append(round(S9p7, 3))

        # Fit including the full AGN model.
        ndim = 9  # Number of parms
        nwalkers = int(2. * ndim)  # Number of walkers

        # Define the starting parms. Each of them has been normalised to a mean of zero to ease convergence.
        parms = np.zeros(shape=(nwalkers, ndim))
        parms[:, 0] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # norm Dust
        parms[:, 1] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # norm PAH
        parms[:, 2] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # norm PL AGN
        parms[:, 3] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # alpha1
        parms[:, 4] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # alpha2
        parms[:, 5] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # alpha3
        parms[:, 6] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # Siem
        parms[:, 7] = np.random.uniform(low=-0.3, high=0.3,
                                        size=nwalkers)  # Si shift
        parms[:, 8] = np.random.uniform(low=-5., high=5.,
                                        size=nwalkers)  # Break

        modelSi11 = Simodel(wavFit / (1. + z), 11., 0.)
        modelSi18 = Simodel(wavFit / (1. + z), 18., 0.)
        modelSi = modelSi18 + modelSi11

        # Set the ensemble sample of Goodman&Weare and run the MCMC for Nmc steps
        sampler = EnsembleSampler(nwalkers, ndim, lnpostfn_spec_wAGN, \
                args = (np.array([Pdust, PPAH, PPL, Palpha, PSi, Pbreak]), \
                modelDust, modelPAH, modelSi, fluxFit, efluxFit, UL, wei, z, wavFit, obsPerWav_AGN, x_red))
        sampler.run_mcmc(parms, Nmc, progress=bool(pgrbar))

        # Build the flat chain after burning 20% of the chain and thinning to every 10 values.
        chain = sampler.get_chain(discard=NburnIn, thin=10, flat=True)
        dfChain = pd.DataFrame(chain)
        dfChain.columns = [
            'logNormDust', 'logNormPAH', 'logNormAGN', 'alpha1', 'alpha2',
            'alpha3', 'logNormSi', 'dSi', 'lBreak'
        ]

        # Save the optimised parameters. Median of the posterior as best fitting value and std dev as 1sigma uncertainties.
        # Dust continuum
        logNorm_Dust_perTempl.append(dfChain['logNormDust'].median())
        elogNorm_Dust_perTempl.append(dfChain['logNormDust'].std())

        # PAH
        logNorm_PAH_perTempl.append(dfChain['logNormPAH'].median())
        elogNorm_PAH_perTempl.append(dfChain['logNormPAH'].std())

        # Norm AGN
        logNormAGN_PL_perTempl.append(dfChain['logNormAGN'].median())
        elogNormAGN_PL_perTempl.append(dfChain['logNormAGN'].std())

        # slope of the first power law
        alpha1_perTempl.append(dfChain['alpha1'].median())
        ealpha1_perTempl.append(dfChain['alpha1'].std())

        # slope of the second power law
        alpha2_perTempl.append(dfChain['alpha2'].median())
        ealpha2_perTempl.append(dfChain['alpha2'].std())

        # slope of the third power law
        alpha3_perTempl.append(dfChain['alpha3'].median())
        ealpha3_perTempl.append(dfChain['alpha3'].std())

        # silicate emisison
        logNorm_Si_perTempl.append(dfChain['logNormSi'].median())
        elogNorm_Si_perTempl.append(dfChain['logNormSi'].std())

        # dSi
        dSi_perTempl.append(dfChain['dSi'].median())
        edSi_perTempl.append(dfChain['dSi'].std())

        # cut off or position of the break
        l_break_PL_perTempl.append(dfChain['lBreak'].median())
        el_break_PL_perTempl.append(dfChain['lBreak'].std())

        # Clauclate the logl of the model
        theta = np.array([logNorm_Dust_perTempl[-1],\
              logNorm_PAH_perTempl[-1],\
              logNormAGN_PL_perTempl[-1],\
              alpha1_perTempl[-1], \
              alpha2_perTempl[-1], \
              alpha3_perTempl[-1], \
              logNorm_Si_perTempl[-1], \
              dSi_perTempl[-1], \
              l_break_PL_perTempl[-1],\
              ])
        Pr = np.array([Pdust, PPAH, PPL, Palpha, PSi, Pbreak])
        logl = lnpostfn_spec_wAGN(theta, Pr, modelDust, modelPAH, modelSi,
                                  fluxFit, efluxFit, UL, wei, z, wavFit,
                                  obsPerWav_AGN, x_red)
        logl_perTempl.append(logl)

        # AGNon = 1 since AGN is accounted for
        AGNon.append(1.)

        # Save the number of parameters
        nParms.append(ndim)

        # Save the name of the galaxy template
        tplName_perTempl.append(name_i)

        # Save the total obscuration at 9.7 microns
        S9p7_save.append(round(S9p7, 3))

    # Find the best model and the Akaike weight
    bestModelInd, Awi = exctractBestModel(logl_perTempl,
                                          nParms,
                                          len(wavFit),
                                          corrected=False)
    bestModelFlag = np.zeros(len(AGNon))
    bestModelFlag[bestModelInd] = 1

    # Save the results in a table
    resDict = {'logNormGal_dust': np.array(logNorm_Dust_perTempl) + Pdust[0], 'elogNormGal_dust': np.abs(np.array(elogNorm_Dust_perTempl)), \
         'logNormGal_PAH': np.array(logNorm_PAH_perTempl) + PPAH[0], 'elogNormGal_PAH': np.abs(np.array(elogNorm_PAH_perTempl)), \
         'logNormAGN_PL': np.array(logNormAGN_PL_perTempl) + PPL[0], 'elogNormAGN_PL': np.abs(np.array(elogNormAGN_PL_perTempl)), \
         'lBreak_PL': np.array(l_break_PL_perTempl) + Pbreak[0], 'elBreak_PL': np.abs(np.array(el_break_PL_perTempl )), \
         'alpha1': np.array(alpha1_perTempl) + Palpha[0], 'ealpha1': np.abs(np.array(ealpha1_perTempl)), \
         'alpha2': np.array(alpha2_perTempl) + Palpha[0], 'ealpha2': np.abs(np.array(ealpha2_perTempl)), \
         'alpha3': np.array(alpha3_perTempl) + Palpha[0], 'ealpha3': np.abs(np.array(ealpha3_perTempl)), \
         'logNorm_Si': np.array(logNorm_Si_perTempl) + PSi[0], 'elogNorm_Si': np.abs(np.array(elogNorm_Si_perTempl)), \
         'dSi': np.array(dSi_perTempl), 'edSi': np.abs(np.array(edSi_perTempl)), \
         'logl': logl_perTempl, 'AGNon': AGNon, 'tplName': tplName_perTempl,\
         'bestModelFlag': bestModelFlag, 'Aw': Awi, 'S9p7': S9p7_save}

    dfRes = pd.DataFrame(resDict)
    return dfRes