Ejemplo n.º 1
0
def perform_casc_fit(config,
                     geom,
                     ps_model,
                     llh,
                     dataset,
                     on_radius=Angle("0.07 deg"),
                     B=1e-16,
                     plot=False):
    """
    Perform the combined cascade fit

    Parameters
    ----------
    config: dict
        Configuration dictionary

    B: float
        B-field strength

    geom:`~gammapy.maps.WcsGeom` object
        geometry of the dataset

    on_radius: `~astropy.coordinates.Angle` object
        Radius of ON region of IACT analysis

    ps_model: `gammapy.model.modeling.models` object
        Assumed point source model

    llh: `simCRpropa.fermiinterp.LogLikeCubeFermi`
        Interpolated Fermi likelihood cube

    dataset: `~gammapy.datasets.SpectrumOnOffDataset`
        The IACT data set

    plot: bool
        If true, generate diagnostic plots

    Returns
    -------
    Tuple containing -2 * ln (likelihood values) for fit
    on Fermi-LAT data only and combined fit
    """

    logging.info(f" ------ B = {B:.2e} ------- ")
    # Load the cascade
    # generate a casc map with low
    # spatial resolution to speed up calculations
    ebl = EBLAbsorptionNormSpectralModel.read(
        filename=config['global']['gammapy_ebl_file'],
        redshift=config[src]['z_src'])

    casc_file = config['global']['casc_file'].replace(
        "*", "{0:.3f}".format(config[src]['z_casc']), 1)
    casc_file = casc_file.replace("*", "{0:.2e}".format(B))
    casc_1d = CascMap.gen_from_hd5f(
        casc_file,
        skycoord=geom.center_skydir,
        width=2 * np.round(on_radius.value / geom.pixel_scales[0].value, 0) *
        geom.pixel_scales[0].value,
        binsz=geom.pixel_scales[0].value,
        ebins=21,
        id_detection=22,
        smooth_kwargs={
            'kernel': Gaussian2DKernel,
            'threshold': 2.,
            'steps': 50
        })
    # Initialize the cascade model
    # use the best fit model for the intrinsic spectrum
    logging.info("Initializing cascade model ...")
    casc_spec = CascadeSpectralModel(casc_1d,
                                     ps_model.spectral_model.model1.copy(),
                                     ebl,
                                     on_region,
                                     rotation=config['global']['rotation'] *
                                     u.deg,
                                     tmax=config['global']['tmax'] * u.yr,
                                     bias=0. * u.dimensionless_unscaled,
                                     energy_unit_spectral_model="TeV",
                                     use_gammapy_interp=False)
    # Plot the total model
    if plot:
        energy_power = 2
        fig = plt.figure(dpi=150)
        ax = fig.add_subplot(111)

        e_range = [1e-3, 10.] * u.TeV
        casc_spec.add_primary = True
        casc_spec.plot(ax=ax, energy_range=e_range, energy_power=energy_power)
        ps_model.spectral_model.plot(ax=ax,
                                     energy_range=e_range,
                                     energy_power=energy_power)
        casc_spec.add_primary = False
        casc_spec.plot(ax=ax, energy_range=e_range, energy_power=energy_power)
        casc_spec.add_primary = True
        plt.ylim(1e-15, 3e-12)

        ax.grid()
        fig.savefig(f"plots/{src:s}_B{B}_casc+ps_models.png")
        plt.close("all")

    # Perform the fit with the cascade
    casc_model = SkyModel(spectral_model=casc_spec, name='casc')
    # limit the parameter ranges and change the tolerance
    # index
    casc_model.parameters['index'].min = llh.params['Index'].min()
    casc_model.parameters['index'].max = llh.params['Index'].max()
    # amplitude
    casc_model.parameters[
        'amplitude'].min = casc_model.parameters['amplitude'].value / 10.
    casc_model.parameters[
        'amplitude'].max = casc_model.parameters['amplitude'].value * 10.
    # cutoff
    casc_model.parameters['lambda_'].min = 1. / llh.params['Cutoff'].max()
    casc_model.parameters['lambda_'].max = 1. / llh.params['Cutoff'].min()
    casc_model.parameters['lambda_'].frozen = config['global']['fix_cutoff']
    # bias between Fermi and HESS
    casc_model.parameters['bias'].value = 0.
    casc_model.parameters['bias'].frozen = config['global']['fix_bias']
    logging.info(f"Initial parameters:\n{casc_model.parameters.to_table()}")
    # interpolate the fermi likelihood for the right B field
    # TODO: changes necessary if more than one coherence length or theta jet used
    idb = np.where(llh.params["B"] == B)[0][0]
    llh.interp_llh(
        llh.params["B"][idb],  # choose a B field - should match casc file
        llh.params["maxTurbScale"]
        [0],  # choose a turb scale - should match casc file
        llh.params["th_jet"]
        [0],  # choose a jet opening angle - should match casc file
        method='linear',
    )
    # plot the interpolation
    if plot:
        fig = plt.figure(dpi=150)
        ax = fig.add_subplot(111)

        # plot a likelihood surface
        # choose a slice in cut off energy
        cut_id = 0
        # build a grid of indices and norms
        ii, nn = np.meshgrid(llh._params["Index"],
                             llh.log_norm_array,
                             indexing='ij')
        # plot the log likehood grid
        dlogl = 2. * (llh._llh_grid[cut_id] - llh._llh_grid[cut_id].max())

        vmin = -100.
        # check if most points have higher dlogl,
        # and if so, adjust color scaling
        if dlogl[dlogl > vmin].size < 10:
            vmin = 3. * dlogl[dlogl < 0].max()

        im = ax.pcolormesh(ii, nn, dlogl, cmap=cmap_name, vmin=vmin, vmax=0)
        ax.annotate("$E_\mathrm{{cut}} = {0:.0f}$TeV".format(
            llh.params["Cutoff"][cut_id]),
                    xy=(0.05, 0.95),
                    xycoords='axes fraction',
                    color='w',
                    va='top',
                    fontsize='x-large')
        plt.colorbar(im, label='$\ln\mathcal{L}$')
        ax.tick_params(direction='out')
        plt.xlabel("$\Gamma$")
        plt.ylabel("$\log_{10}(N)$")
        plt.grid(color='0.7', ls=':')
        plt.subplots_adjust(bottom=0.2, left=0.2)
        fig.savefig(
            "plots/{1:s}_lnl_fermi_grid_Ecut{0:.0f}TeV_B{2}.png".format(
                llh.params["Cutoff"][cut_id], src, B))
        plt.close("all")
    # initialize prior data set
    logging.info("Initializing data set with priors")
    prior_stack = PriorSpectrumDatasetOnOff.from_spectrum_dataset_fermi_interp(
        dataset, llh_fermi_interp=None)
    # add the interpolator to the data set
    prior_stack.llh_fermi_interp = llh.interp
    # add the reference energy at which interpolation was performed (in MeV)
    prior_stack.ref_energy = src_dict['spectral_pars']['Scale']['value']
    prior_stack.models = casc_model
    logging.info("Performing combined fit")
    fit_casc = Fit([prior_stack])
    fit_result_casc = fit_casc.run(optimize_opts=dict(
        print_level=2, tol=10., migrad_opts=dict(ncall=1000)))
    logging.info(f"Parameters after fit:\n{casc_model.parameters.to_table()}")
    logging.info(f"Total stat after fit {fit_result_casc.total_stat}")
    logging.info(f"Fermi logl after fit {prior_stack.llh_fermi}")

    # plot the flux points
    if plot:
        fig = plt.figure(dpi=150)
        ax = flux_points.plot(energy_power=2., label='data', marker='o')
        if not casc_model.parameters['bias'].value == 0.:
            fp = flux_points.table.copy()
            fp['e_ref'] *= 1. + casc_model.parameters['bias'].value
            fp['e_min'] *= 1. + casc_model.parameters['bias'].value
            fp['e_max'] *= 1. + casc_model.parameters['bias'].value
            fp_rescale = FluxPoints(fp)
            fp_rescale.plot(ax=ax,
                            energy_power=2.,
                            label='data rescaled',
                            marker='o',
                            color='green')

        # plot the final model
        e_range = [1e-3, 10.] * u.TeV
        # total model
        casc_model.spectral_model.add_primary = True
        casc_model.spectral_model.plot(ax=ax,
                                       energy_range=e_range,
                                       energy_power=2,
                                       label='Total',
                                       color='k')
        casc_model.spectral_model.plot_error(ax=ax,
                                             energy_range=e_range,
                                             energy_power=2)
        # point source
        obs_model = casc_model.spectral_model.intrinsic_spectral_model * casc_model.spectral_model.ebl

        # cascade
        casc_model.spectral_model.add_primary = False
        casc_model.spectral_model.plot(ax=ax,
                                       energy_range=e_range,
                                       energy_power=2,
                                       label='Cascade',
                                       color='k',
                                       ls='-.')
        casc_model.spectral_model.add_primary = True

        if casc_model.parameters['bias'].value == 0.:
            obs_model.plot(ax=ax,
                           energy_range=e_range,
                           energy_power=2,
                           label='Point source',
                           color='k',
                           ls='--')
        else:
            casc_model.parameters['bias'].value = 0.
            obs_model.plot(ax=ax,
                           energy_range=e_range,
                           energy_power=2,
                           label='Point source',
                           color='green',
                           ls='--')
            casc_model.spectral_model.plot(ax=ax,
                                           energy_range=e_range,
                                           energy_power=2,
                                           label='Total, bias = 0',
                                           color='green',
                                           ls=':')

            # cascade
            casc_model.spectral_model.add_primary = False
            casc_model.spectral_model.plot(ax=ax,
                                           energy_range=e_range,
                                           energy_power=2,
                                           label='Cascade',
                                           color='green',
                                           ls='-.')
            casc_model.spectral_model.add_primary = True
            pass

        # fermi SED
        SEDPlotter.plot_sed(
            sed,
            ax=ax,
            ms=6.,
            marker='.',
            color='C1',
            mec='C1',
            alpha=1.,
            noline=False,
            band_alpha=0.2,
            line_alpha=0.5,
            band_linestyle='-',
            label="Fermi",
            flux_unit='TeV cm-2 s-1',
            energy_unit='TeV',
            print_name=False,
            # apply bias to fermi data, in fit it's the other way around
            #bias=casc_model.parameters['bias'].value
        )

        ax.legend(loc='lower center')
        plt.ylim(3e-15, 2e-10)
        plt.xlim(1e-3, 2e1)
        fig.savefig(f"plots/final_fits_{src:s}_b{B}.png")
        plt.close("all")

    # plot a likelihood surface for best cut value
    if plot:
        par_amp = prior_stack.models['casc'].parameters['amplitude']
        amp = np.logspace(
            np.log10(par_amp.value) - 1.,
            np.log10(par_amp.value) + .5, 100)
        amp *= par_amp.unit

        par_idx = prior_stack.models['casc'].parameters['index']
        idx = np.linspace(par_idx.value - .5, par_idx.value + 1., 120)
        idx *= par_idx.unit
        ii, aa = np.meshgrid(idx, amp, indexing='ij')
        c_stat = prior_stack.get_llh_fermi(amplitude=aa.reshape(-1),
                                           index=ii.reshape(-1),
                                           set_attribute=False).reshape(
                                               aa.shape)
        #ii, nn = np.meshgrid(idx, llh.log_norm_array, indexing='ij')
        #c_stat = np.zeros((idx.shape[0], amp.shape[0]))
        #for i, iidx in enumerate(idx):
        #for j, a in enumerate(amp):
        #c_stat[i,j] = prior_stack.get_llh_fermi(amplitude=a, index=iidx, set_attribute=False)
        #c_stat = np.zeros((idx.shape[0], llh.log_norm_array.shape[0]))
        #for i, iidx in enumerate(idx.value):
        #    for j, log_norm in enumerate(llh.log_norm_array):
        #        c_stat[i,j] = prior_stack.get_llh_fermi(amplitude=10.**log_norm * u.Unit('MeV-1 s-1 cm-2'),
        #                                                index=iidx * u.dimensionless_unscaled,
        #                                                set_attribute=False,
        #                                                reference=prior_stack._ref_energy * u.MeV)
        d_cstat = 2. * (c_stat - c_stat.min())

        fig = plt.figure(dpi=150)
        ax = fig.add_subplot(111)

        vmin = 0.
        vmax = 300.

        im = ax.pcolormesh(ii.value,
                           np.log10(aa.value),
                           d_cstat,
                           cmap=cmap_name,
                           vmin=vmin,
                           vmax=vmax)
        #im = ax.pcolormesh(ii, nn, d_cstat, cmap=cmap_name, vmin=vmin, vmax=vmax)
        ax.annotate("$E_\mathrm{{cut}} = {0:.2f}$TeV, $B={1:.2e}$G".format(
            1. / prior_stack.models['casc'].parameters['lambda_'].value, B),
                    xy=(0.05, 0.95),
                    xycoords='axes fraction',
                    color='k',
                    va='top',
                    fontsize='x-large')
        plt.colorbar(im, label='$-2\Delta\ln\mathcal{L}$')
        ax.plot(par_idx.value,
                np.log10(par_amp.value),
                ms=10.,
                marker='*',
                mec='k')
        ax.tick_params(direction='out')
        plt.xlabel("$\Gamma$")
        plt.ylabel(
            "$\log_{10}(N_\mathrm{H.E.S.S.}) = \log_{10}((E_{0,{Fermi}} / E_{0,\mathrm{H.E.S.S.}})^{-\Gamma}N_{Fermi})$",
            fontsize='medium')
        plt.grid(color='0.7', ls=':')
        plt.subplots_adjust(bottom=0.2, left=0.2)
        fig.savefig("plots/{0:s}_lnl_fermi_interp_B{1}.png".format(src, B))
        plt.close("all")

    return prior_stack, prior_stack.llh_fermi, fit_result_casc.total_stat
Ejemplo n.º 2
0
        llh.get_llh_one_source(src, norm_key='dnde_src')

        # Define the model for the intrinsic / observed blazar spectrum
        epl = ExpCutoffPowerLawSpectralModel(
            amplitude=1e-12 * u.Unit("cm-2 s-1 TeV-1"),
            index=1.6 * u.dimensionless_unscaled,
            lambda_=0.1 * u.Unit("TeV-1"),
            reference=1 * u.TeV,
        )
        #epl.parameters['lambda_'].min = 0.
        #epl.parameters['lambda_'].max = 1.
        epl.parameters['lambda_'].min = 1. / llh.params['Cutoff'].max()
        epl.parameters['lambda_'].max = 1. / llh.params['Cutoff'].min()

        ebl = EBLAbsorptionNormSpectralModel.read(
            filename=config['global']['gammapy_ebl_file'],
            redshift=config[src]['z_src'])

        obs = epl * ebl

        # fit the point source model
        # without the cascade
        logging.info("Fitting IACT data with point source")
        ps_model = SkyModel(spectral_model=obs.copy(), name='ps')
        dataset_stack[0].models = ps_model
        fit_1d = Fit(dataset_stack)
        fit_result_ps = fit_1d.run(optimize_opts=dict(
            print_level=1, tol=0.1, migrad_opts=dict(ncall=1000)))

        ps_model.parameters.to_table()
        ps_model_table = ps_model.spectral_model.model1.parameters.to_table()