# Licensed under a 3-clause BSD style license - see LICENSE.rstimportloggingimportnumpyasnpfromastropyimportunitsasufromastropy.tableimportTablefromastropy.visualizationimportquantity_supportimportmatplotlib.pyplotaspltfrommatplotlib.gridspecimportGridSpecfromgammapy.modeling.modelsimportDatasetModelsfromgammapy.utils.scriptsimportmake_name,make_pathfrom.coreimportDatasetlog=logging.getLogger(__name__)__all__=["FluxPointsDataset"]
[docs]classFluxPointsDataset(Dataset):""" Bundle a set of flux points with a parametric model, to compute fit statistic function using chi2 statistics. Parameters ---------- models : `~gammapy.modeling.models.Models` Models (only spectral part needs to be set) data : `~gammapy.estimators.FluxPoints` Flux points. Must be sorted along the energy axis mask_fit : `numpy.ndarray` Mask to apply for fitting mask_safe : `numpy.ndarray` Mask defining the safe data range. By default upper limit values are excluded. meta_table : `~astropy.table.Table` Table listing information on observations used to create the dataset. One line per observation for stacked datasets. Examples -------- Load flux points from file and fit with a power-law model:: >>> from gammapy.modeling import Fit >>> from gammapy.modeling.models import PowerLawSpectralModel, SkyModel >>> from gammapy.estimators import FluxPoints >>> from gammapy.datasets import FluxPointsDataset >>> filename = "$GAMMAPY_DATA/tests/spectrum/flux_points/diff_flux_points.fits" >>> dataset = FluxPointsDataset.read(filename) >>> model = SkyModel(spectral_model=PowerLawSpectralModel()) >>> dataset.models = model Make the fit >>> fit = Fit() >>> result = fit.run([dataset]) >>> print(result) OptimizeResult <BLANKLINE> backend : minuit method : migrad success : True message : Optimization terminated successfully. nfev : 135 total stat : 25.21 <BLANKLINE> CovarianceResult <BLANKLINE> backend : minuit method : hesse success : True message : Hesse terminated successfully. >>> print(result.parameters.to_table()) type name value unit ... max frozen is_norm link -------- --------- ---------- -------------- ... --- ------ ------- ---- spectral index 2.2159e+00 ... nan False False spectral amplitude 2.1619e-13 cm-2 s-1 TeV-1 ... nan False True spectral reference 1.0000e+00 TeV ... nan True False Note: In order to reproduce the example you need the tests datasets folder. You may download it with the command ``gammapy download datasets --tests --out $GAMMAPY_DATA`` """stat_type="chi2"tag="FluxPointsDataset"def__init__(self,models=None,data=None,mask_fit=None,mask_safe=None,name=None,meta_table=None,):ifdata.geom.ndim!=3ornotdata.geom.has_energy_axis:raiseValueError("FluxPointsDataset only supports an energy axis")self.data=dataself.mask_fit=mask_fitself._name=make_name(name)self.models=modelsself.meta_table=meta_tableifmask_safeisNone:mask_safe=(~data.is_ul).data[:,0,0]self.mask_safe=mask_safe@propertydefname(self):returnself._name@propertydefgti(self):"""Good time interval info (`GTI`)"""returnself.data.gti@propertydefmodels(self):returnself._models@models.setterdefmodels(self,models):ifmodelsisNone:self._models=Noneelse:models=DatasetModels(models)self._models=models.select(datasets_names=self.name)
[docs]defwrite(self,filename,overwrite=False,**kwargs):"""Write flux point dataset to file. Parameters ---------- filename : str Filename to write to. overwrite : bool Overwrite existing file. **kwargs : dict Keyword arguments passed to `~astropy.table.Table.write`. """table=self.data.to_table()ifself.mask_fitisNone:mask_fit=self.mask_safeelse:mask_fit=self.mask_fittable["mask_fit"]=mask_fittable["mask_safe"]=self.mask_safetable.write(make_path(filename),overwrite=overwrite,**kwargs)
[docs]@classmethoddefread(cls,filename,name=None,format="gadf-sed"):"""Read pre-computed flux points and create a dataset Parameters ---------- filename : str Filename to read from. name : str Name of the new dataset. format : {"gadf-sed"} Format of the dataset file. Returns ------- dataset : `FluxPointsDataset` FluxPointsDataset """fromgammapy.estimatorsimportFluxPointsfilename=make_path(filename)table=Table.read(filename)mask_fit=Nonemask_safe=Noneif"mask_safe"intable.colnames:mask_safe=table["mask_safe"].data.astype("bool")if"mask_fit"intable.colnames:mask_fit=table["mask_fit"].data.astype("bool")returncls(name=make_name(name),data=FluxPoints.from_table(table,format=format),mask_fit=mask_fit,mask_safe=mask_safe,)
[docs]@classmethoddeffrom_dict(cls,data,**kwargs):"""Create flux point dataset from dict. Parameters ---------- data : dict Dict containing data to create dataset from. Returns ------- dataset : `FluxPointsDataset` Flux point datasets. """fromgammapy.estimatorsimportFluxPointsfilename=make_path(data["filename"])table=Table.read(filename)mask_fit=table["mask_fit"].data.astype("bool")mask_safe=table["mask_safe"].data.astype("bool")table.remove_columns(["mask_fit","mask_safe"])returncls(name=data["name"],data=FluxPoints.from_table(table,format="gadf-sed"),mask_fit=mask_fit,mask_safe=mask_safe,)
def__str__(self):str_=f"{self.__class__.__name__}\n"str_+="-"*len(self.__class__.__name__)+"\n"str_+="\n"str_+="\t{:32}: {}\n\n".format("Name",self.name)# data sectionn_bins=0ifself.dataisnotNone:n_bins=self.data.energy_axis.nbinstr_+="\t{:32}: {}\n".format("Number of total flux points",n_bins)n_fit_bins=0ifself.maskisnotNone:n_fit_bins=np.sum(self.mask.data)str_+="\t{:32}: {}\n\n".format("Number of fit bins",n_fit_bins)# likelihood sectionstr_+="\t{:32}: {}\n".format("Fit statistic type",self.stat_type)stat=np.nanifself.dataisnotNoneandself.modelsisnotNone:stat=self.stat_sum()str_+="\t{:32}: {:.2f}\n\n".format("Fit statistic value (-2 log(L))",stat)# model sectionn_models=0ifself.modelsisnotNone:n_models=len(self.models)str_+="\t{:32}: {}\n".format("Number of models",n_models)ifself.modelsisnotNone:str_+="\t{:32}: {}\n".format("Number of parameters",len(self.models.parameters))str_+="\t{:32}: {}\n\n".format("Number of free parameters",len(self.models.parameters.free_parameters))str_+="\t"+"\n\t".join(str(self.models).split("\n")[2:])returnstr_.expandtabs(tabsize=2)
[docs]defdata_shape(self):"""Shape of the flux points data (tuple)."""returnself.data.energy_ref.shape
[docs]defresiduals(self,method="diff"):"""Compute flux point residuals Parameters ---------- method: {"diff", "diff/model"} Method used to compute the residuals. Available options are: - `diff` (default): data - model - `diff/model`: (data - model) / model Returns ------- residuals : `~numpy.ndarray` Residuals array. """fp=self.datamodel=self.flux_pred()residuals=self._compute_residuals(fp.dnde.quantity[:,0,0],model,method)# Remove residuals for upper_limitsresiduals[fp.is_ul.data[:,0,0]]=np.nanreturnresiduals
[docs]defplot_fit(self,ax_spectrum=None,ax_residuals=None,kwargs_spectrum=None,kwargs_residuals=None,):"""Plot flux points, best fit model and residuals in two panels. Calls `~FluxPointsDataset.plot_spectrum` and `~FluxPointsDataset.plot_residuals`. Parameters ---------- ax_spectrum : `~matplotlib.axes.Axes` Axes to plot flux points and best fit model on. ax_residuals : `~matplotlib.axes.Axes` Axes to plot residuals on. kwargs_spectrum : dict Keyword arguments passed to `~FluxPointsDataset.plot_spectrum`. kwargs_residuals : dict Keyword arguments passed to `~FluxPointsDataset.plot_residuals`. Returns ------- ax_spectrum, ax_residuals : `~matplotlib.axes.Axes` Flux points, best fit model and residuals plots. Examples -------- >>> from gammapy.modeling.models import PowerLawSpectralModel, SkyModel >>> from gammapy.estimators import FluxPoints >>> from gammapy.datasets import FluxPointsDataset >>> #load precomputed flux points >>> filename = "$GAMMAPY_DATA/tests/spectrum/flux_points/diff_flux_points.fits" >>> flux_points = FluxPoints.read(filename) >>> model = SkyModel(spectral_model=PowerLawSpectralModel()) >>> dataset = FluxPointsDataset(model, flux_points) >>> #configuring optional parameters >>> kwargs_spectrum = {"kwargs_model": {"color":"red", "ls":"--"}, "kwargs_fp":{"color":"green", "marker":"o"}} >>> kwargs_residuals = {"color": "blue", "markersize":4, "marker":'s', } >>> dataset.plot_fit(kwargs_residuals=kwargs_residuals, kwargs_spectrum=kwargs_spectrum) # doctest: +SKIP """fig=plt.figure(figsize=(9,7))gs=GridSpec(7,1)ifax_spectrumisNone:ax_spectrum=fig.add_subplot(gs[:5,:])ifax_residualsisNone:ax_residuals=fig.add_subplot(gs[5:,:],sharex=ax_spectrum)kwargs_spectrum=kwargs_spectrumor{}kwargs_residuals=kwargs_residualsor{}kwargs_residuals.setdefault("method","diff/model")self.plot_spectrum(ax=ax_spectrum,**kwargs_spectrum)self.plot_residuals(ax=ax_residuals,**kwargs_residuals)returnax_spectrum,ax_residuals
[docs]defplot_residuals(self,ax=None,method="diff",**kwargs):"""Plot flux point residuals. Parameters ---------- ax : `~matplotlib.axes.Axes` Axes to plot on. method : {"diff", "diff/model"} Normalization used to compute the residuals, see `FluxPointsDataset.residuals`. **kwargs : dict Keyword arguments passed to `~matplotlib.axes.Axes.errorbar`. Returns ------- ax : `~matplotlib.axes.Axes` Axes object. """ax=axorplt.gca()fp=self.dataresiduals=self.residuals(method)xerr=self.data.energy_axis.as_plot_xerryerr=fp._plot_get_flux_err(sed_type="dnde")ifmethod=="diff/model":model=self.flux_pred()yerr=(yerr[0].quantity[:,0,0]/model),(yerr[1].quantity[:,0,0]/model)elifmethod=="diff":yerr=yerr[0].quantity[:,0,0],yerr[1].quantity[:,0,0]else:raiseValueError('Invalid method, choose between "diff" and "diff/model"')kwargs.setdefault("color",kwargs.pop("c","black"))kwargs.setdefault("marker","+")kwargs.setdefault("linestyle",kwargs.pop("ls","none"))withquantity_support():ax.errorbar(fp.energy_ref,residuals,xerr=xerr,yerr=yerr,**kwargs)ax.axhline(0,color=kwargs["color"],lw=0.5)# format axesax.set_xlabel(f"Energy [{self._energy_unit}]")ax.set_xscale("log")label=self._residuals_labels[method]ax.set_ylabel(f"Residuals\n{label}")ymin=np.nanmin(residuals-yerr[0])ymax=np.nanmax(residuals+yerr[1])ymax=max(abs(ymin),ymax)ax.set_ylim(-1.05*ymax,1.05*ymax)returnax
[docs]defplot_spectrum(self,ax=None,kwargs_fp=None,kwargs_model=None):"""Plot spectrum including flux points and model. Parameters ---------- ax : `~matplotlib.axes.Axes` Axes to plot on. kwargs_fp : dict Keyword arguments passed to `gammapy.estimators.FluxPoints.plot`. kwargs_model : dict Keyword arguments passed to `gammapy.modeling.models.SpectralModel.plot` and `gammapy.modeling.models.SpectralModel.plot_error`. Returns ------- ax : `~matplotlib.axes.Axes` Axes object. Examples -------- >>> from gammapy.modeling.models import PowerLawSpectralModel, SkyModel >>> from gammapy.estimators import FluxPoints >>> from gammapy.datasets import FluxPointsDataset >>> #load precomputed flux points >>> filename = "$GAMMAPY_DATA/tests/spectrum/flux_points/diff_flux_points.fits" >>> flux_points = FluxPoints.read(filename) >>> model = SkyModel(spectral_model=PowerLawSpectralModel()) >>> dataset = FluxPointsDataset(model, flux_points) >>> #configuring optional parameters >>> kwargs_model = {"color":"red", "ls":"--"} >>> kwargs_fp = {"color":"green", "marker":"o"} >>> dataset.plot_spectrum(kwargs_fp=kwargs_fp, kwargs_model=kwargs_model) # doctest: +SKIP """kwargs_fp=(kwargs_fpor{}).copy()kwargs_model=(kwargs_modelor{}).copy()# plot flux pointskwargs_fp.setdefault("label","Flux points")kwargs_fp.setdefault("sed_type","e2dnde")ax=self.data.plot(ax,**kwargs_fp)kwargs_model.setdefault("energy_bounds",self._energy_bounds)kwargs_model.setdefault("label","Best fit model")kwargs_model.setdefault("sed_type","e2dnde")kwargs_model.setdefault("zorder",10)formodelinself.models:ifmodel.datasets_namesisNoneorself.nameinmodel.datasets_names:model.spectral_model.plot(ax=ax,**kwargs_model)kwargs_model["color"]=ax.lines[-1].get_color()kwargs_model.pop("label")formodelinself.models:ifmodel.datasets_namesisNoneorself.nameinmodel.datasets_names:model.spectral_model.plot_error(ax=ax,**kwargs_model)returnax